@mcp-z/oauth 1.0.0 → 1.0.1

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 (48) hide show
  1. package/dist/cjs/account-utils.js.map +1 -1
  2. package/dist/cjs/index.d.cts +1 -1
  3. package/dist/cjs/index.d.ts +1 -1
  4. package/dist/cjs/index.js.map +1 -1
  5. package/dist/cjs/jwt-auth.js.map +1 -1
  6. package/dist/cjs/key-utils.js.map +1 -1
  7. package/dist/cjs/lib/account-server/index.js.map +1 -1
  8. package/dist/cjs/lib/account-server/loopback.js +27 -19
  9. package/dist/cjs/lib/account-server/loopback.js.map +1 -1
  10. package/dist/cjs/lib/account-server/me.js.map +1 -1
  11. package/dist/cjs/lib/account-server/shared-utils.js.map +1 -1
  12. package/dist/cjs/lib/account-server/stateless.js.map +1 -1
  13. package/dist/cjs/lib/account-server/types.d.cts +2 -2
  14. package/dist/cjs/lib/account-server/types.d.ts +2 -2
  15. package/dist/cjs/lib/account-server/types.js.map +1 -1
  16. package/dist/cjs/lib/dcr-types.js.map +1 -1
  17. package/dist/cjs/lib/rfc-metadata-types.js.map +1 -1
  18. package/dist/cjs/pkce.js.map +1 -1
  19. package/dist/cjs/sanitizer.js.map +1 -1
  20. package/dist/cjs/schemas/index.js.map +1 -1
  21. package/dist/cjs/session-auth.js.map +1 -1
  22. package/dist/cjs/templates.js.map +1 -1
  23. package/dist/cjs/types.d.cts +3 -2
  24. package/dist/cjs/types.d.ts +3 -2
  25. package/dist/cjs/types.js.map +1 -1
  26. package/dist/esm/account-utils.js.map +1 -1
  27. package/dist/esm/index.d.ts +1 -1
  28. package/dist/esm/index.js.map +1 -1
  29. package/dist/esm/jwt-auth.js.map +1 -1
  30. package/dist/esm/key-utils.js.map +1 -1
  31. package/dist/esm/lib/account-server/index.js.map +1 -1
  32. package/dist/esm/lib/account-server/loopback.js +8 -5
  33. package/dist/esm/lib/account-server/loopback.js.map +1 -1
  34. package/dist/esm/lib/account-server/me.js.map +1 -1
  35. package/dist/esm/lib/account-server/shared-utils.js.map +1 -1
  36. package/dist/esm/lib/account-server/stateless.js.map +1 -1
  37. package/dist/esm/lib/account-server/types.d.ts +2 -2
  38. package/dist/esm/lib/account-server/types.js.map +1 -1
  39. package/dist/esm/lib/dcr-types.js.map +1 -1
  40. package/dist/esm/lib/rfc-metadata-types.js.map +1 -1
  41. package/dist/esm/pkce.js.map +1 -1
  42. package/dist/esm/sanitizer.js.map +1 -1
  43. package/dist/esm/schemas/index.js.map +1 -1
  44. package/dist/esm/session-auth.js.map +1 -1
  45. package/dist/esm/templates.js.map +1 -1
  46. package/dist/esm/types.d.ts +3 -2
  47. package/dist/esm/types.js.map +1 -1
  48. package/package.json +2 -2
@@ -1 +1 @@
1
- {"version":3,"sources":["/Users/kevin/Dev/Projects/ai/mcp-z/oauth/oauth/src/lib/dcr-types.ts"],"sourcesContent":["/**\n * Dynamic Client Registration (DCR) types per RFC 7591\n *\n * Defines core types for OAuth 2.0 Dynamic Client Registration Protocol.\n * Used by providers to register clients dynamically with authorization servers.\n *\n * @see https://datatracker.ietf.org/doc/html/rfc7591\n */\n\nimport type { Logger } from '../types.ts';\n\n/**\n * Client metadata for dynamic registration request (RFC 7591 Section 2)\n *\n * All fields are optional per RFC 7591. Authorization server may have\n * required fields or default values based on policy.\n */\nexport interface DcrClientMetadata {\n /** Array of redirection URI strings for redirect-based flows */\n redirect_uris?: string[];\n\n /** Client authentication method for token endpoint */\n token_endpoint_auth_method?: 'none' | 'client_secret_post' | 'client_secret_basic';\n\n /** OAuth 2.0 grant types the client may use */\n grant_types?: string[];\n\n /** OAuth 2.0 response types the client may use */\n response_types?: string[];\n\n /** Human-readable client name */\n client_name?: string;\n\n /** URL providing information about the client */\n client_uri?: string;\n\n /** URL referencing a logo for the client */\n logo_uri?: string;\n\n /** Space-separated list of scope values */\n scope?: string;\n\n /** Array of contact strings (typically email addresses) */\n contacts?: string[];\n\n /** URL pointing to terms of service document */\n tos_uri?: string;\n\n /** URL pointing to privacy policy document */\n policy_uri?: string;\n\n /** URL referencing the client's JSON Web Key Set */\n jwks_uri?: string;\n\n /** Client's JSON Web Key Set document value */\n jwks?: object;\n\n /** Unique identifier for the client software */\n software_id?: string;\n\n /** Version identifier for the client software */\n software_version?: string;\n\n /** JWT containing client metadata claims (signed software statement) */\n software_statement?: string;\n}\n\n/**\n * Client information response from successful registration (RFC 7591 Section 3.2.1)\n *\n * Authorization server returns client credentials and echoes/modifies metadata.\n * client_id is always returned, client_secret is optional for public clients.\n */\nexport interface DcrClientInformation {\n /** REQUIRED: OAuth 2.0 client identifier string */\n client_id: string;\n\n /** OPTIONAL: OAuth 2.0 client secret (omitted for public clients) */\n client_secret?: string;\n\n /** OPTIONAL: Timestamp of client ID issuance (seconds since Unix epoch) */\n client_id_issued_at?: number;\n\n /**\n * REQUIRED if client_secret issued: Expiration timestamp (seconds since epoch)\n * Value of 0 indicates the secret does not expire\n */\n client_secret_expires_at?: number;\n\n // All registered metadata fields (echoed or server-modified)\n redirect_uris?: string[];\n token_endpoint_auth_method?: string;\n grant_types?: string[];\n response_types?: string[];\n client_name?: string;\n client_uri?: string;\n logo_uri?: string;\n scope?: string;\n contacts?: string[];\n tos_uri?: string;\n policy_uri?: string;\n jwks_uri?: string;\n jwks?: object;\n software_id?: string;\n software_version?: string;\n}\n\n/**\n * Provider tokens for stateless DCR pattern\n *\n * In stateless mode, DCR provider receives provider credentials from context\n * rather than managing token storage. Used for MCP server deployments where\n * client manages all tokens.\n */\nexport interface ProviderTokens {\n /** OAuth 2.0 access token for provider API calls */\n accessToken: string;\n\n /** Optional refresh token for token renewal */\n refreshToken?: string;\n\n /** Token expiration timestamp (seconds since Unix epoch) */\n expiresAt?: number;\n\n /** Space-separated list of granted scopes */\n scope?: string;\n}\n\n/**\n * Configuration for DCR provider initialization\n *\n * Minimal config for creating DCR provider instances. Additional provider-specific\n * config (client IDs, secrets, redirect URIs) handled by concrete implementations.\n */\nexport interface DcrConfig {\n /** Authorization server's registration endpoint URL */\n registrationEndpoint: string;\n\n /** Client metadata to register with authorization server */\n metadata: DcrClientMetadata;\n\n /** Optional logger for DCR operations */\n logger?: Logger;\n}\n\n/**\n * DCR error response per RFC 7591 Section 3.2.2\n *\n * Authorization server returns HTTP 400 with error details when\n * registration fails due to invalid metadata or policy violations.\n */\nexport interface DcrErrorResponse {\n /** REQUIRED: Single ASCII error code string */\n error: 'invalid_redirect_uri' | 'invalid_client_metadata' | 'invalid_software_statement' | 'unapproved_software_statement' | string;\n\n /** OPTIONAL: Human-readable ASCII description */\n error_description?: string;\n}\n"],"names":[],"mappings":"AAAA;;;;;;;CAOC"}
1
+ {"version":3,"sources":["/Users/kevin/Dev/Projects/mcp-z/oauth/src/lib/dcr-types.ts"],"sourcesContent":["/**\n * Dynamic Client Registration (DCR) types per RFC 7591\n *\n * Defines core types for OAuth 2.0 Dynamic Client Registration Protocol.\n * Used by providers to register clients dynamically with authorization servers.\n *\n * @see https://datatracker.ietf.org/doc/html/rfc7591\n */\n\nimport type { Logger } from '../types.ts';\n\n/**\n * Client metadata for dynamic registration request (RFC 7591 Section 2)\n *\n * All fields are optional per RFC 7591. Authorization server may have\n * required fields or default values based on policy.\n */\nexport interface DcrClientMetadata {\n /** Array of redirection URI strings for redirect-based flows */\n redirect_uris?: string[];\n\n /** Client authentication method for token endpoint */\n token_endpoint_auth_method?: 'none' | 'client_secret_post' | 'client_secret_basic';\n\n /** OAuth 2.0 grant types the client may use */\n grant_types?: string[];\n\n /** OAuth 2.0 response types the client may use */\n response_types?: string[];\n\n /** Human-readable client name */\n client_name?: string;\n\n /** URL providing information about the client */\n client_uri?: string;\n\n /** URL referencing a logo for the client */\n logo_uri?: string;\n\n /** Space-separated list of scope values */\n scope?: string;\n\n /** Array of contact strings (typically email addresses) */\n contacts?: string[];\n\n /** URL pointing to terms of service document */\n tos_uri?: string;\n\n /** URL pointing to privacy policy document */\n policy_uri?: string;\n\n /** URL referencing the client's JSON Web Key Set */\n jwks_uri?: string;\n\n /** Client's JSON Web Key Set document value */\n jwks?: object;\n\n /** Unique identifier for the client software */\n software_id?: string;\n\n /** Version identifier for the client software */\n software_version?: string;\n\n /** JWT containing client metadata claims (signed software statement) */\n software_statement?: string;\n}\n\n/**\n * Client information response from successful registration (RFC 7591 Section 3.2.1)\n *\n * Authorization server returns client credentials and echoes/modifies metadata.\n * client_id is always returned, client_secret is optional for public clients.\n */\nexport interface DcrClientInformation {\n /** REQUIRED: OAuth 2.0 client identifier string */\n client_id: string;\n\n /** OPTIONAL: OAuth 2.0 client secret (omitted for public clients) */\n client_secret?: string;\n\n /** OPTIONAL: Timestamp of client ID issuance (seconds since Unix epoch) */\n client_id_issued_at?: number;\n\n /**\n * REQUIRED if client_secret issued: Expiration timestamp (seconds since epoch)\n * Value of 0 indicates the secret does not expire\n */\n client_secret_expires_at?: number;\n\n // All registered metadata fields (echoed or server-modified)\n redirect_uris?: string[];\n token_endpoint_auth_method?: string;\n grant_types?: string[];\n response_types?: string[];\n client_name?: string;\n client_uri?: string;\n logo_uri?: string;\n scope?: string;\n contacts?: string[];\n tos_uri?: string;\n policy_uri?: string;\n jwks_uri?: string;\n jwks?: object;\n software_id?: string;\n software_version?: string;\n}\n\n/**\n * Provider tokens for stateless DCR pattern\n *\n * In stateless mode, DCR provider receives provider credentials from context\n * rather than managing token storage. Used for MCP server deployments where\n * client manages all tokens.\n */\nexport interface ProviderTokens {\n /** OAuth 2.0 access token for provider API calls */\n accessToken: string;\n\n /** Optional refresh token for token renewal */\n refreshToken?: string;\n\n /** Token expiration timestamp (seconds since Unix epoch) */\n expiresAt?: number;\n\n /** Space-separated list of granted scopes */\n scope?: string;\n}\n\n/**\n * Configuration for DCR provider initialization\n *\n * Minimal config for creating DCR provider instances. Additional provider-specific\n * config (client IDs, secrets, redirect URIs) handled by concrete implementations.\n */\nexport interface DcrConfig {\n /** Authorization server's registration endpoint URL */\n registrationEndpoint: string;\n\n /** Client metadata to register with authorization server */\n metadata: DcrClientMetadata;\n\n /** Optional logger for DCR operations */\n logger?: Logger;\n}\n\n/**\n * DCR error response per RFC 7591 Section 3.2.2\n *\n * Authorization server returns HTTP 400 with error details when\n * registration fails due to invalid metadata or policy violations.\n */\nexport interface DcrErrorResponse {\n /** REQUIRED: Single ASCII error code string */\n error: 'invalid_redirect_uri' | 'invalid_client_metadata' | 'invalid_software_statement' | 'unapproved_software_statement' | string;\n\n /** OPTIONAL: Human-readable ASCII description */\n error_description?: string;\n}\n"],"names":[],"mappings":"AAAA;;;;;;;CAOC"}
@@ -1 +1 @@
1
- {"version":3,"sources":["/Users/kevin/Dev/Projects/ai/mcp-z/oauth/oauth/src/lib/rfc-metadata-types.ts"],"sourcesContent":["/**\n * RFC 8414 Authorization Server Metadata\n * @see https://www.rfc-editor.org/rfc/rfc8414.html\n */\nexport interface RFC8414Metadata {\n /** Authorization server issuer URL */\n issuer: string;\n /** OAuth 2.0 authorization endpoint */\n authorization_endpoint: string;\n /** OAuth 2.0 token endpoint */\n token_endpoint: string;\n /** Dynamic Client Registration endpoint (RFC 7591) */\n registration_endpoint: string;\n /** Optional: Token revocation endpoint */\n revocation_endpoint?: string;\n /** Optional: Supported OAuth scopes */\n scopes_supported?: string[];\n /** Optional: Supported response types */\n response_types_supported?: string[];\n /** Optional: Supported grant types */\n grant_types_supported?: string[];\n /** Optional: Supported token endpoint auth methods */\n token_endpoint_auth_methods_supported?: string[];\n /** Optional: Supported PKCE code challenge methods (RFC 7636) */\n code_challenge_methods_supported?: string[];\n /** Optional: Service documentation URL */\n service_documentation?: string;\n /** Allow additional provider-specific fields */\n [key: string]: unknown;\n}\n\n/**\n * RFC 9728 Protected Resource Metadata\n * @see https://www.rfc-editor.org/rfc/rfc9728.html\n */\nexport interface RFC9728Metadata {\n /** Protected resource URL */\n resource: string;\n /** List of authorization servers that can issue tokens for this resource */\n authorization_servers: string[];\n /** OAuth scopes supported by this resource */\n scopes_supported: string[];\n /** Methods for providing bearer tokens (typically ['header']) */\n bearer_methods_supported: string[];\n /** Allow additional provider-specific fields */\n [key: string]: unknown;\n}\n"],"names":[],"mappings":"AAAA;;;CAGC"}
1
+ {"version":3,"sources":["/Users/kevin/Dev/Projects/mcp-z/oauth/src/lib/rfc-metadata-types.ts"],"sourcesContent":["/**\n * RFC 8414 Authorization Server Metadata\n * @see https://www.rfc-editor.org/rfc/rfc8414.html\n */\nexport interface RFC8414Metadata {\n /** Authorization server issuer URL */\n issuer: string;\n /** OAuth 2.0 authorization endpoint */\n authorization_endpoint: string;\n /** OAuth 2.0 token endpoint */\n token_endpoint: string;\n /** Dynamic Client Registration endpoint (RFC 7591) */\n registration_endpoint: string;\n /** Optional: Token revocation endpoint */\n revocation_endpoint?: string;\n /** Optional: Supported OAuth scopes */\n scopes_supported?: string[];\n /** Optional: Supported response types */\n response_types_supported?: string[];\n /** Optional: Supported grant types */\n grant_types_supported?: string[];\n /** Optional: Supported token endpoint auth methods */\n token_endpoint_auth_methods_supported?: string[];\n /** Optional: Supported PKCE code challenge methods (RFC 7636) */\n code_challenge_methods_supported?: string[];\n /** Optional: Service documentation URL */\n service_documentation?: string;\n /** Allow additional provider-specific fields */\n [key: string]: unknown;\n}\n\n/**\n * RFC 9728 Protected Resource Metadata\n * @see https://www.rfc-editor.org/rfc/rfc9728.html\n */\nexport interface RFC9728Metadata {\n /** Protected resource URL */\n resource: string;\n /** List of authorization servers that can issue tokens for this resource */\n authorization_servers: string[];\n /** OAuth scopes supported by this resource */\n scopes_supported: string[];\n /** Methods for providing bearer tokens (typically ['header']) */\n bearer_methods_supported: string[];\n /** Allow additional provider-specific fields */\n [key: string]: unknown;\n}\n"],"names":[],"mappings":"AAAA;;;CAGC"}
@@ -1 +1 @@
1
- {"version":3,"sources":["/Users/kevin/Dev/Projects/ai/mcp-z/oauth/oauth/src/pkce.ts"],"sourcesContent":["/**\n * PKCE (Proof Key for Code Exchange) utilities for OAuth 2.0\n *\n * Implements RFC 7636 PKCE extension for public OAuth clients.\n * Generates cryptographically secure code verifier and challenge.\n */\n\nimport { createHash, randomBytes } from 'crypto';\n\n/**\n * PKCE code verifier and challenge pair\n */\nexport interface PKCEPair {\n /** Code verifier - random string sent to token endpoint */\n verifier: string;\n /** Code challenge - SHA256 hash of verifier sent to authorization endpoint */\n challenge: string;\n}\n\n/**\n * Generate PKCE code verifier and challenge pair\n *\n * Uses SHA-256 hashing (S256 method) as recommended by RFC 7636.\n * Code verifier is 32 random bytes base64url-encoded (43 characters).\n *\n * @returns PKCE pair with verifier and challenge\n *\n * @example\n * ```typescript\n * const { verifier, challenge } = generatePKCE();\n *\n * // Use challenge in authorization URL\n * authUrl.searchParams.set('code_challenge', challenge);\n * authUrl.searchParams.set('code_challenge_method', 'S256');\n *\n * // Later, use verifier in token exchange\n * tokenParams.code_verifier = verifier;\n * ```\n */\nexport function generatePKCE(): PKCEPair {\n const verifier = randomBytes(32).toString('base64url');\n const challenge = createHash('sha256').update(verifier).digest('base64url');\n\n return {\n verifier,\n challenge,\n };\n}\n"],"names":["generatePKCE","verifier","randomBytes","toString","challenge","createHash","update","digest"],"mappings":"AAAA;;;;;CAKC;;;;+BAkCeA;;;eAAAA;;;sBAhCwB;AAgCjC,SAASA;IACd,IAAMC,WAAWC,IAAAA,mBAAW,EAAC,IAAIC,QAAQ,CAAC;IAC1C,IAAMC,YAAYC,IAAAA,kBAAU,EAAC,UAAUC,MAAM,CAACL,UAAUM,MAAM,CAAC;IAE/D,OAAO;QACLN,UAAAA;QACAG,WAAAA;IACF;AACF"}
1
+ {"version":3,"sources":["/Users/kevin/Dev/Projects/mcp-z/oauth/src/pkce.ts"],"sourcesContent":["/**\n * PKCE (Proof Key for Code Exchange) utilities for OAuth 2.0\n *\n * Implements RFC 7636 PKCE extension for public OAuth clients.\n * Generates cryptographically secure code verifier and challenge.\n */\n\nimport { createHash, randomBytes } from 'crypto';\n\n/**\n * PKCE code verifier and challenge pair\n */\nexport interface PKCEPair {\n /** Code verifier - random string sent to token endpoint */\n verifier: string;\n /** Code challenge - SHA256 hash of verifier sent to authorization endpoint */\n challenge: string;\n}\n\n/**\n * Generate PKCE code verifier and challenge pair\n *\n * Uses SHA-256 hashing (S256 method) as recommended by RFC 7636.\n * Code verifier is 32 random bytes base64url-encoded (43 characters).\n *\n * @returns PKCE pair with verifier and challenge\n *\n * @example\n * ```typescript\n * const { verifier, challenge } = generatePKCE();\n *\n * // Use challenge in authorization URL\n * authUrl.searchParams.set('code_challenge', challenge);\n * authUrl.searchParams.set('code_challenge_method', 'S256');\n *\n * // Later, use verifier in token exchange\n * tokenParams.code_verifier = verifier;\n * ```\n */\nexport function generatePKCE(): PKCEPair {\n const verifier = randomBytes(32).toString('base64url');\n const challenge = createHash('sha256').update(verifier).digest('base64url');\n\n return {\n verifier,\n challenge,\n };\n}\n"],"names":["generatePKCE","verifier","randomBytes","toString","challenge","createHash","update","digest"],"mappings":"AAAA;;;;;CAKC;;;;+BAkCeA;;;eAAAA;;;sBAhCwB;AAgCjC,SAASA;IACd,IAAMC,WAAWC,IAAAA,mBAAW,EAAC,IAAIC,QAAQ,CAAC;IAC1C,IAAMC,YAAYC,IAAAA,kBAAU,EAAC,UAAUC,MAAM,CAACL,UAAUM,MAAM,CAAC;IAE/D,OAAO;QACLN,UAAAA;QACAG,WAAAA;IACF;AACF"}
@@ -1 +1 @@
1
- {"version":3,"sources":["/Users/kevin/Dev/Projects/ai/mcp-z/oauth/oauth/src/sanitizer.ts"],"sourcesContent":["/**\n * Data sanitization utilities for secure logging.\n * Redacts sensitive OAuth tokens, API keys, and credentials from log output.\n *\n * @example\n * ```typescript\n * sanitizeData({ accountId: 'test@example.com', access_token: 'secret_token_value' })\n * // { accountId: 'test@example.com', access_token: 'secr****alue' }\n *\n * sanitizeForLogging('Processing token', { token: 'secret_value' })\n * // { message: 'Processing token', meta: { token: 'secr****alue' } }\n * ```\n */\n\n/** Regex patterns for sensitive data that should be redacted from logs */\nconst SENSITIVE_PATTERNS = [\n // OAuth tokens, codes, and secrets\n /access_token['\":\\s]*['\"]\\s*([^'\"]+)['\"]/gi,\n /(access_token_[a-zA-Z0-9_]+)/gi,\n /refresh_token['\":\\s]*['\"]\\s*([^'\"]+)['\"]/gi,\n /client_secret['\":\\s]*['\"]\\s*([^'\"]+)['\"]/gi,\n /id_token['\":\\s]*['\"]\\s*([^'\"]+)['\"]/gi,\n /\\bcode['\":\\s]*['\"]\\s*([^'\"]+)['\"]/gi,\n /\\bstate['\":\\s]*['\"]\\s*([^'\"]+)['\"]/gi,\n /code_verifier['\":\\s]*['\"]\\s*([^'\"]+)['\"]/gi,\n /code_challenge['\":\\s]*['\"]\\s*([^'\"]+)['\"]/gi,\n /codeVerifier['\":\\s]*['\"]\\s*([^'\"]+)['\"]/gi,\n /codeChallenge['\":\\s]*['\"]\\s*([^'\"]+)['\"]/gi,\n /device_code['\":\\s]*['\"]\\s*([^'\"]+)['\"]/gi,\n /user_code['\":\\s]*['\"]\\s*([^'\"]+)['\"]/gi,\n /verification_uri['\":\\s]*['\"]\\s*([^'\"]+)['\"]/gi,\n /verification_uri_complete['\":\\s]*['\"]\\s*([^'\"]+)['\"]/gi,\n\n // Provider credentials and identifiers\n /app_secret['\":\\s]*['\"]\\s*([^'\"]+)['\"]/gi,\n /appSecret['\":\\s]*['\"]\\s*([^'\"]+)['\"]/gi,\n /tenant_id['\":\\s]*['\"]\\s*([^'\"]+)['\"]/gi,\n /tenantId['\":\\s]*['\"]\\s*([^'\"]+)['\"]/gi,\n /client_id['\":\\s]*['\"]\\s*([^'\"]+)['\"]/gi,\n /clientId['\":\\s]*['\"]\\s*([^'\"]+)['\"]/gi,\n /app_id['\":\\s]*['\"]\\s*([^'\"]+)['\"]/gi,\n /appId['\":\\s]*['\"]\\s*([^'\"]+)['\"]/gi,\n /redirect_uri['\":\\s]*['\"]\\s*([^'\"]+)['\"]/gi,\n /redirectUri['\":\\s]*['\"]\\s*([^'\"]+)['\"]/gi,\n /subscription_key['\":\\s]*['\"]\\s*([^'\"]+)['\"]/gi,\n /subscriptionKey['\":\\s]*['\"]\\s*([^'\"]+)['\"]/gi,\n\n // Security secrets and keys\n /webhook_secret['\":\\s]*['\"]\\s*([^'\"]+)['\"]/gi,\n /webhookSecret['\":\\s]*['\"]\\s*([^'\"]+)['\"]/gi,\n /signing_secret['\":\\s]*['\"]\\s*([^'\"]+)['\"]/gi,\n /signingSecret['\":\\s]*['\"]\\s*([^'\"]+)['\"]/gi,\n /encryption_key['\":\\s]*['\"]\\s*([^'\"]+)['\"]/gi,\n /encryptionKey['\":\\s]*['\"]\\s*([^'\"]+)['\"]/gi,\n /private_key['\":\\s]*['\"]\\s*([^'\"]+)['\"]/gi,\n /privateKey['\":\\s]*['\"]\\s*([^'\"]+)['\"]/gi,\n /certificate['\":\\s]*['\"]\\s*([^'\"]+)['\"]/gi,\n /cert['\":\\s]*['\"]\\s*([^'\"]+)['\"]/gi,\n\n // Authorization headers\n /Authorization['\":\\s]*['\"]\\s*Bearer\\s+([^'\"]+)['\"]/gi,\n /authorization['\":\\s]*['\"]\\s*Bearer\\s+([^'\"]+)['\"]/gi,\n /Bearer\\s+([A-Za-z0-9+/=\\-_.]+)/gi,\n /Authorization:\\s*Bearer\\s+([A-Za-z0-9+/=\\-_.]+)/gi,\n /[A-Z_]+_(SECRET|KEY|TOKEN|PASSWORD)['\":\\s]*['\"]\\s*([^'\"]+)['\"]/gi,\n\n // Session and CSRF tokens\n /\\bnonce['\":\\s]*['\"]\\s*([^'\"]+)['\"]/gi,\n /session[_-]?id['\":\\s]*['\"]\\s*([^'\"]+)['\"]/gi,\n /csrf[_-]?token['\":\\s]*['\"]\\s*([^'\"]+)['\"]/gi,\n\n // Other sensitive patterns\n /\"email\"\\s*:\\s*\"([^@\"]{1,64}@[^.\"]{1,63}\\.[a-z]{2,6})\"/gi,\n /api[_-]?key['\":\\s]*['\"]\\s*([^'\"]+)['\"]/gi,\n /password['\":\\s]*['\"]\\s*([^'\"]+)['\"]/gi,\n /\\b(ey[A-Za-z0-9+/=]+\\.[A-Za-z0-9+/=]+\\.[A-Za-z0-9+/=\\-_]+)/g,\n\n // Base64 secrets (split into length ranges for practical matching)\n /\\b([A-Za-z0-9+/]{60,200}={0,2})\\b/g,\n /\\b([A-Za-z0-9+/]{201,1000}={0,2})\\b/g,\n /\\b([A-Za-z0-9+/]{1001,5000}={0,2})\\b/g,\n\n // Connection identifiers\n /connection[_-]?id['\":\\s]*['\"]\\s*([0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12})['\"]/gi,\n];\n\n/** Field names that should be redacted when found as object keys */\nconst SENSITIVE_FIELDS = new Set([\n 'access_token',\n 'accessToken',\n 'refresh_token',\n 'refreshToken',\n 'client_secret',\n 'clientSecret',\n 'id_token',\n 'idToken',\n 'code',\n 'authorization_code',\n 'authorizationCode',\n 'device_code',\n 'deviceCode',\n 'user_code',\n 'userCode',\n 'verification_uri',\n 'verificationUri',\n 'verification_uri_complete',\n 'verificationUriComplete',\n 'client_id',\n 'clientId',\n 'app_id',\n 'appId',\n 'app_secret',\n 'appSecret',\n 'tenant_id',\n 'tenantId',\n 'bot_id',\n 'botId',\n 'workspace_id',\n 'workspaceId',\n 'organization_id',\n 'organizationId',\n 'redirect_uri',\n 'redirectUri',\n 'audience',\n 'realm',\n 'domain',\n 'webhook_secret',\n 'webhookSecret',\n 'signing_secret',\n 'signingSecret',\n 'subscription_key',\n 'subscriptionKey',\n 'encryption_key',\n 'encryptionKey',\n 'private_key',\n 'privateKey',\n 'certificate',\n 'cert',\n 'stripe-signature',\n 'x-hub-signature',\n 'x-hub-signature-256',\n 'x-slack-signature',\n 'x-mcp-z-webhook-secret',\n 'password',\n 'secret',\n 'token',\n 'authorization',\n 'credential',\n 'auth',\n 'verifier',\n 'challenge',\n 'code_verifier',\n 'codeVerifier',\n 'code_challenge',\n 'codeChallenge',\n 'nonce',\n 'session_id',\n 'sessionId',\n 'csrf_token',\n 'csrfToken',\n 'api_key',\n 'apiKey',\n 'state',\n 'connection_id',\n 'connectionId',\n 'gmail_connection_id',\n 'gmailConnectionId',\n]);\n\nfunction isAlreadySanitized(value: string): boolean {\n return value.includes('****') || value.includes('[REDACTED]') || value === '[REDACTED]';\n}\n\nfunction redactValue(value: string): string {\n if (isAlreadySanitized(value)) {\n return value;\n }\n\n if (value.length <= 8) {\n return '*'.repeat(value.length);\n }\n\n // Show first 4 and last 4 characters\n return `${value.substring(0, 4)}****${value.substring(value.length - 4)}`;\n}\n\nexport function sanitizeData(data: unknown): unknown {\n if (typeof data === 'string') {\n if (isAlreadySanitized(data)) {\n return data;\n }\n\n let sanitized = data;\n for (const pattern of SENSITIVE_PATTERNS) {\n sanitized = sanitized.replace(pattern, (match, captured) => {\n if (typeof captured === 'string') {\n const redacted = redactValue(captured);\n return match.replace(captured, redacted);\n }\n return match;\n });\n }\n\n return sanitized;\n }\n\n if (Array.isArray(data)) {\n return data.map(sanitizeData);\n }\n\n if (data && typeof data === 'object') {\n const sanitized: Record<string, unknown> = {};\n\n for (const [key, value] of Object.entries(data)) {\n const lowerKey = key.toLowerCase();\n\n if (SENSITIVE_FIELDS.has(lowerKey) || SENSITIVE_FIELDS.has(key)) {\n if (typeof value === 'string') {\n sanitized[key] = redactValue(value);\n } else {\n sanitized[key] = '[REDACTED]';\n }\n } else {\n sanitized[key] = sanitizeData(value);\n }\n }\n\n return sanitized;\n }\n\n return data;\n}\n\n/**\n * Prevent log injection attacks by escaping control characters\n * SECURITY: Critical for preventing CRLF injection (OWASP A03)\n */\nexport function sanitizeLogMessage(message: string, maxLength = 50000): string {\n if (typeof message !== 'string') {\n return String(message);\n }\n\n // Truncation protection - prevent log poisoning via huge payloads\n let processedMessage = message;\n if (processedMessage.length > maxLength) {\n processedMessage = `${processedMessage.substring(0, maxLength)} [TRUNCATED]`;\n }\n\n return (\n processedMessage\n .normalize('NFKC')\n .replace(/\\r\\n|\\r|\\n/g, ' ')\n .replace(/\\t/g, ' ')\n // biome-ignore lint/suspicious/noControlCharactersInRegex: Security sanitization requires control character removal\n .replace(/[\\x00-\\x1F\\x7F-\\x9F]/g, '')\n .replace(/[\\u200B-\\u200D\\uFEFF]/g, '') // Zero-width chars used for obfuscation\n .trim()\n );\n}\n\n/**\n * Sanitize log message and metadata for safe logging\n * Applies both CRLF protection and sensitive data redaction\n *\n * @param message - The log message to sanitize\n * @param meta - Optional metadata object to sanitize\n * @param enableDataSanitization - Whether to apply sensitive data redaction (default: true)\n * @returns Sanitized message and metadata ready for logging\n */\nexport function sanitizeForLogging(message: string, meta?: Record<string, unknown>, enableDataSanitization = true): { message: string; meta: Record<string, unknown> } {\n const cleanMessage = sanitizeLogMessage(message);\n\n if (!enableDataSanitization) {\n return {\n message: cleanMessage,\n meta: meta || {},\n };\n }\n\n return {\n message: sanitizeData(cleanMessage) as string,\n meta: sanitizeData(meta || {}) as Record<string, unknown>,\n };\n}\n\nexport function sanitizeForLoggingFormatter() {\n return {\n log: (obj) => {\n const message = (obj.msg || obj.message || '') as string;\n const { message: clean, meta } = sanitizeForLogging(message, obj as Record<string, unknown>);\n return { ...meta, msg: clean };\n },\n };\n}\n"],"names":["sanitizeData","sanitizeForLogging","sanitizeForLoggingFormatter","sanitizeLogMessage","SENSITIVE_PATTERNS","SENSITIVE_FIELDS","Set","isAlreadySanitized","value","includes","redactValue","length","repeat","substring","data","sanitized","pattern","replace","match","captured","redacted","Array","isArray","map","Object","entries","key","lowerKey","toLowerCase","has","message","maxLength","String","processedMessage","normalize","trim","meta","enableDataSanitization","cleanMessage","log","obj","msg","clean"],"mappings":";;;;;;;;;;;QA0LgBA;eAAAA;;QAmFAC;eAAAA;;QAgBAC;eAAAA;;QAhDAC;eAAAA;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;AA7OhB;;;;;;;;;;;;CAYC,GAED,wEAAwE,GACxE,IAAMC,qBAAqB;IACzB,mCAAmC;IACnC;IACA;IACA;IACA;IACA;IACA;IACA;IACA;IACA;IACA;IACA;IACA;IACA;IACA;IACA;IAEA,uCAAuC;IACvC;IACA;IACA;IACA;IACA;IACA;IACA;IACA;IACA;IACA;IACA;IACA;IAEA,4BAA4B;IAC5B;IACA;IACA;IACA;IACA;IACA;IACA;IACA;IACA;IACA;IAEA,wBAAwB;IACxB;IACA;IACA;IACA;IACA;IAEA,0BAA0B;IAC1B;IACA;IACA;IAEA,2BAA2B;IAC3B;IACA;IACA;IACA;IAEA,mEAAmE;IACnE;IACA;IACA;IAEA,yBAAyB;IACzB;CACD;AAED,kEAAkE,GAClE,IAAMC,mBAAmB,IAAIC,IAAI;IAC/B;IACA;IACA;IACA;IACA;IACA;IACA;IACA;IACA;IACA;IACA;IACA;IACA;IACA;IACA;IACA;IACA;IACA;IACA;IACA;IACA;IACA;IACA;IACA;IACA;IACA;IACA;IACA;IACA;IACA;IACA;IACA;IACA;IACA;IACA;IACA;IACA;IACA;IACA;IACA;IACA;IACA;IACA;IACA;IACA;IACA;IACA;IACA;IACA;IACA;IACA;IACA;IACA;IACA;IACA;IACA;IACA;IACA;IACA;IACA;IACA;IACA;IACA;IACA;IACA;IACA;IACA;IACA;IACA;IACA;IACA;IACA;IACA;IACA;IACA;IACA;IACA;IACA;IACA;CACD;AAED,SAASC,mBAAmBC,KAAa;IACvC,OAAOA,MAAMC,QAAQ,CAAC,WAAWD,MAAMC,QAAQ,CAAC,iBAAiBD,UAAU;AAC7E;AAEA,SAASE,YAAYF,KAAa;IAChC,IAAID,mBAAmBC,QAAQ;QAC7B,OAAOA;IACT;IAEA,IAAIA,MAAMG,MAAM,IAAI,GAAG;QACrB,OAAO,IAAIC,MAAM,CAACJ,MAAMG,MAAM;IAChC;IAEA,qCAAqC;IACrC,OAAO,AAAC,GAA8BH,OAA5BA,MAAMK,SAAS,CAAC,GAAG,IAAG,QAAwC,OAAlCL,MAAMK,SAAS,CAACL,MAAMG,MAAM,GAAG;AACvE;AAEO,SAASX,aAAac,IAAa;IACxC,IAAI,OAAOA,SAAS,UAAU;QAC5B,IAAIP,mBAAmBO,OAAO;YAC5B,OAAOA;QACT;QAEA,IAAIC,YAAYD;YACX,kCAAA,2BAAA;;YAAL,QAAK,YAAiBV,uCAAjB,SAAA,6BAAA,QAAA,yBAAA,iCAAqC;gBAArC,IAAMY,UAAN;gBACHD,YAAYA,UAAUE,OAAO,CAACD,SAAS,SAACE,OAAOC;oBAC7C,IAAI,OAAOA,aAAa,UAAU;wBAChC,IAAMC,WAAWV,YAAYS;wBAC7B,OAAOD,MAAMD,OAAO,CAACE,UAAUC;oBACjC;oBACA,OAAOF;gBACT;YACF;;YARK;YAAA;;;qBAAA,6BAAA;oBAAA;;;oBAAA;0BAAA;;;;QAUL,OAAOH;IACT;IAEA,IAAIM,MAAMC,OAAO,CAACR,OAAO;QACvB,OAAOA,KAAKS,GAAG,CAACvB;IAClB;IAEA,IAAIc,QAAQ,CAAA,OAAOA,qCAAP,SAAOA,KAAG,MAAM,UAAU;QACpC,IAAMC,aAAqC,CAAC;YAEvC,mCAAA,4BAAA;;YAAL,QAAK,aAAsBS,OAAOC,OAAO,CAACX,0BAArC,UAAA,8BAAA,SAAA,0BAAA,kCAA4C;gBAA5C,mCAAA,kBAAOY,sBAAKlB;gBACf,IAAMmB,WAAWD,IAAIE,WAAW;gBAEhC,IAAIvB,iBAAiBwB,GAAG,CAACF,aAAatB,iBAAiBwB,GAAG,CAACH,MAAM;oBAC/D,IAAI,OAAOlB,UAAU,UAAU;wBAC7BO,UAAS,CAACW,IAAI,GAAGhB,YAAYF;oBAC/B,OAAO;wBACLO,UAAS,CAACW,IAAI,GAAG;oBACnB;gBACF,OAAO;oBACLX,UAAS,CAACW,IAAI,GAAG1B,aAAaQ;gBAChC;YACF;;YAZK;YAAA;;;qBAAA,8BAAA;oBAAA;;;oBAAA;0BAAA;;;;QAcL,OAAOO;IACT;IAEA,OAAOD;AACT;AAMO,SAASX,mBAAmB2B,OAAe;QAAEC,YAAAA,iEAAY;IAC9D,IAAI,OAAOD,YAAY,UAAU;QAC/B,OAAOE,OAAOF;IAChB;IAEA,kEAAkE;IAClE,IAAIG,mBAAmBH;IACvB,IAAIG,iBAAiBtB,MAAM,GAAGoB,WAAW;QACvCE,mBAAmB,AAAC,GAA2C,OAAzCA,iBAAiBpB,SAAS,CAAC,GAAGkB,YAAW;IACjE;IAEA,OACEE,iBACGC,SAAS,CAAC,QACVjB,OAAO,CAAC,eAAe,KACvBA,OAAO,CAAC,OAAO,IAChB,oHAAoH;KACnHA,OAAO,CAAC,yBAAyB,IACjCA,OAAO,CAAC,0BAA0B,IAAI,wCAAwC;KAC9EkB,IAAI;AAEX;AAWO,SAASlC,mBAAmB6B,OAAe,EAAEM,IAA8B;QAAEC,yBAAAA,iEAAyB;IAC3G,IAAMC,eAAenC,mBAAmB2B;IAExC,IAAI,CAACO,wBAAwB;QAC3B,OAAO;YACLP,SAASQ;YACTF,MAAMA,QAAQ,CAAC;QACjB;IACF;IAEA,OAAO;QACLN,SAAS9B,aAAasC;QACtBF,MAAMpC,aAAaoC,QAAQ,CAAC;IAC9B;AACF;AAEO,SAASlC;IACd,OAAO;QACLqC,KAAK,SAACC;YACJ,IAAMV,UAAWU,IAAIC,GAAG,IAAID,IAAIV,OAAO,IAAI;YAC3C,IAAiC7B,sBAAAA,mBAAmB6B,SAASU,MAArDV,AAASY,QAAgBzC,oBAAzB6B,SAAgBM,OAASnC,oBAATmC;YACxB,OAAO,wCAAKA;gBAAMK,KAAKC;;QACzB;IACF;AACF"}
1
+ {"version":3,"sources":["/Users/kevin/Dev/Projects/mcp-z/oauth/src/sanitizer.ts"],"sourcesContent":["/**\n * Data sanitization utilities for secure logging.\n * Redacts sensitive OAuth tokens, API keys, and credentials from log output.\n *\n * @example\n * ```typescript\n * sanitizeData({ accountId: 'test@example.com', access_token: 'secret_token_value' })\n * // { accountId: 'test@example.com', access_token: 'secr****alue' }\n *\n * sanitizeForLogging('Processing token', { token: 'secret_value' })\n * // { message: 'Processing token', meta: { token: 'secr****alue' } }\n * ```\n */\n\n/** Regex patterns for sensitive data that should be redacted from logs */\nconst SENSITIVE_PATTERNS = [\n // OAuth tokens, codes, and secrets\n /access_token['\":\\s]*['\"]\\s*([^'\"]+)['\"]/gi,\n /(access_token_[a-zA-Z0-9_]+)/gi,\n /refresh_token['\":\\s]*['\"]\\s*([^'\"]+)['\"]/gi,\n /client_secret['\":\\s]*['\"]\\s*([^'\"]+)['\"]/gi,\n /id_token['\":\\s]*['\"]\\s*([^'\"]+)['\"]/gi,\n /\\bcode['\":\\s]*['\"]\\s*([^'\"]+)['\"]/gi,\n /\\bstate['\":\\s]*['\"]\\s*([^'\"]+)['\"]/gi,\n /code_verifier['\":\\s]*['\"]\\s*([^'\"]+)['\"]/gi,\n /code_challenge['\":\\s]*['\"]\\s*([^'\"]+)['\"]/gi,\n /codeVerifier['\":\\s]*['\"]\\s*([^'\"]+)['\"]/gi,\n /codeChallenge['\":\\s]*['\"]\\s*([^'\"]+)['\"]/gi,\n /device_code['\":\\s]*['\"]\\s*([^'\"]+)['\"]/gi,\n /user_code['\":\\s]*['\"]\\s*([^'\"]+)['\"]/gi,\n /verification_uri['\":\\s]*['\"]\\s*([^'\"]+)['\"]/gi,\n /verification_uri_complete['\":\\s]*['\"]\\s*([^'\"]+)['\"]/gi,\n\n // Provider credentials and identifiers\n /app_secret['\":\\s]*['\"]\\s*([^'\"]+)['\"]/gi,\n /appSecret['\":\\s]*['\"]\\s*([^'\"]+)['\"]/gi,\n /tenant_id['\":\\s]*['\"]\\s*([^'\"]+)['\"]/gi,\n /tenantId['\":\\s]*['\"]\\s*([^'\"]+)['\"]/gi,\n /client_id['\":\\s]*['\"]\\s*([^'\"]+)['\"]/gi,\n /clientId['\":\\s]*['\"]\\s*([^'\"]+)['\"]/gi,\n /app_id['\":\\s]*['\"]\\s*([^'\"]+)['\"]/gi,\n /appId['\":\\s]*['\"]\\s*([^'\"]+)['\"]/gi,\n /redirect_uri['\":\\s]*['\"]\\s*([^'\"]+)['\"]/gi,\n /redirectUri['\":\\s]*['\"]\\s*([^'\"]+)['\"]/gi,\n /subscription_key['\":\\s]*['\"]\\s*([^'\"]+)['\"]/gi,\n /subscriptionKey['\":\\s]*['\"]\\s*([^'\"]+)['\"]/gi,\n\n // Security secrets and keys\n /webhook_secret['\":\\s]*['\"]\\s*([^'\"]+)['\"]/gi,\n /webhookSecret['\":\\s]*['\"]\\s*([^'\"]+)['\"]/gi,\n /signing_secret['\":\\s]*['\"]\\s*([^'\"]+)['\"]/gi,\n /signingSecret['\":\\s]*['\"]\\s*([^'\"]+)['\"]/gi,\n /encryption_key['\":\\s]*['\"]\\s*([^'\"]+)['\"]/gi,\n /encryptionKey['\":\\s]*['\"]\\s*([^'\"]+)['\"]/gi,\n /private_key['\":\\s]*['\"]\\s*([^'\"]+)['\"]/gi,\n /privateKey['\":\\s]*['\"]\\s*([^'\"]+)['\"]/gi,\n /certificate['\":\\s]*['\"]\\s*([^'\"]+)['\"]/gi,\n /cert['\":\\s]*['\"]\\s*([^'\"]+)['\"]/gi,\n\n // Authorization headers\n /Authorization['\":\\s]*['\"]\\s*Bearer\\s+([^'\"]+)['\"]/gi,\n /authorization['\":\\s]*['\"]\\s*Bearer\\s+([^'\"]+)['\"]/gi,\n /Bearer\\s+([A-Za-z0-9+/=\\-_.]+)/gi,\n /Authorization:\\s*Bearer\\s+([A-Za-z0-9+/=\\-_.]+)/gi,\n /[A-Z_]+_(SECRET|KEY|TOKEN|PASSWORD)['\":\\s]*['\"]\\s*([^'\"]+)['\"]/gi,\n\n // Session and CSRF tokens\n /\\bnonce['\":\\s]*['\"]\\s*([^'\"]+)['\"]/gi,\n /session[_-]?id['\":\\s]*['\"]\\s*([^'\"]+)['\"]/gi,\n /csrf[_-]?token['\":\\s]*['\"]\\s*([^'\"]+)['\"]/gi,\n\n // Other sensitive patterns\n /\"email\"\\s*:\\s*\"([^@\"]{1,64}@[^.\"]{1,63}\\.[a-z]{2,6})\"/gi,\n /api[_-]?key['\":\\s]*['\"]\\s*([^'\"]+)['\"]/gi,\n /password['\":\\s]*['\"]\\s*([^'\"]+)['\"]/gi,\n /\\b(ey[A-Za-z0-9+/=]+\\.[A-Za-z0-9+/=]+\\.[A-Za-z0-9+/=\\-_]+)/g,\n\n // Base64 secrets (split into length ranges for practical matching)\n /\\b([A-Za-z0-9+/]{60,200}={0,2})\\b/g,\n /\\b([A-Za-z0-9+/]{201,1000}={0,2})\\b/g,\n /\\b([A-Za-z0-9+/]{1001,5000}={0,2})\\b/g,\n\n // Connection identifiers\n /connection[_-]?id['\":\\s]*['\"]\\s*([0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12})['\"]/gi,\n];\n\n/** Field names that should be redacted when found as object keys */\nconst SENSITIVE_FIELDS = new Set([\n 'access_token',\n 'accessToken',\n 'refresh_token',\n 'refreshToken',\n 'client_secret',\n 'clientSecret',\n 'id_token',\n 'idToken',\n 'code',\n 'authorization_code',\n 'authorizationCode',\n 'device_code',\n 'deviceCode',\n 'user_code',\n 'userCode',\n 'verification_uri',\n 'verificationUri',\n 'verification_uri_complete',\n 'verificationUriComplete',\n 'client_id',\n 'clientId',\n 'app_id',\n 'appId',\n 'app_secret',\n 'appSecret',\n 'tenant_id',\n 'tenantId',\n 'bot_id',\n 'botId',\n 'workspace_id',\n 'workspaceId',\n 'organization_id',\n 'organizationId',\n 'redirect_uri',\n 'redirectUri',\n 'audience',\n 'realm',\n 'domain',\n 'webhook_secret',\n 'webhookSecret',\n 'signing_secret',\n 'signingSecret',\n 'subscription_key',\n 'subscriptionKey',\n 'encryption_key',\n 'encryptionKey',\n 'private_key',\n 'privateKey',\n 'certificate',\n 'cert',\n 'stripe-signature',\n 'x-hub-signature',\n 'x-hub-signature-256',\n 'x-slack-signature',\n 'x-mcp-z-webhook-secret',\n 'password',\n 'secret',\n 'token',\n 'authorization',\n 'credential',\n 'auth',\n 'verifier',\n 'challenge',\n 'code_verifier',\n 'codeVerifier',\n 'code_challenge',\n 'codeChallenge',\n 'nonce',\n 'session_id',\n 'sessionId',\n 'csrf_token',\n 'csrfToken',\n 'api_key',\n 'apiKey',\n 'state',\n 'connection_id',\n 'connectionId',\n 'gmail_connection_id',\n 'gmailConnectionId',\n]);\n\nfunction isAlreadySanitized(value: string): boolean {\n return value.includes('****') || value.includes('[REDACTED]') || value === '[REDACTED]';\n}\n\nfunction redactValue(value: string): string {\n if (isAlreadySanitized(value)) {\n return value;\n }\n\n if (value.length <= 8) {\n return '*'.repeat(value.length);\n }\n\n // Show first 4 and last 4 characters\n return `${value.substring(0, 4)}****${value.substring(value.length - 4)}`;\n}\n\nexport function sanitizeData(data: unknown): unknown {\n if (typeof data === 'string') {\n if (isAlreadySanitized(data)) {\n return data;\n }\n\n let sanitized = data;\n for (const pattern of SENSITIVE_PATTERNS) {\n sanitized = sanitized.replace(pattern, (match, captured) => {\n if (typeof captured === 'string') {\n const redacted = redactValue(captured);\n return match.replace(captured, redacted);\n }\n return match;\n });\n }\n\n return sanitized;\n }\n\n if (Array.isArray(data)) {\n return data.map(sanitizeData);\n }\n\n if (data && typeof data === 'object') {\n const sanitized: Record<string, unknown> = {};\n\n for (const [key, value] of Object.entries(data)) {\n const lowerKey = key.toLowerCase();\n\n if (SENSITIVE_FIELDS.has(lowerKey) || SENSITIVE_FIELDS.has(key)) {\n if (typeof value === 'string') {\n sanitized[key] = redactValue(value);\n } else {\n sanitized[key] = '[REDACTED]';\n }\n } else {\n sanitized[key] = sanitizeData(value);\n }\n }\n\n return sanitized;\n }\n\n return data;\n}\n\n/**\n * Prevent log injection attacks by escaping control characters\n * SECURITY: Critical for preventing CRLF injection (OWASP A03)\n */\nexport function sanitizeLogMessage(message: string, maxLength = 50000): string {\n if (typeof message !== 'string') {\n return String(message);\n }\n\n // Truncation protection - prevent log poisoning via huge payloads\n let processedMessage = message;\n if (processedMessage.length > maxLength) {\n processedMessage = `${processedMessage.substring(0, maxLength)} [TRUNCATED]`;\n }\n\n return (\n processedMessage\n .normalize('NFKC')\n .replace(/\\r\\n|\\r|\\n/g, ' ')\n .replace(/\\t/g, ' ')\n // biome-ignore lint/suspicious/noControlCharactersInRegex: Security sanitization requires control character removal\n .replace(/[\\x00-\\x1F\\x7F-\\x9F]/g, '')\n .replace(/[\\u200B-\\u200D\\uFEFF]/g, '') // Zero-width chars used for obfuscation\n .trim()\n );\n}\n\n/**\n * Sanitize log message and metadata for safe logging\n * Applies both CRLF protection and sensitive data redaction\n *\n * @param message - The log message to sanitize\n * @param meta - Optional metadata object to sanitize\n * @param enableDataSanitization - Whether to apply sensitive data redaction (default: true)\n * @returns Sanitized message and metadata ready for logging\n */\nexport function sanitizeForLogging(message: string, meta?: Record<string, unknown>, enableDataSanitization = true): { message: string; meta: Record<string, unknown> } {\n const cleanMessage = sanitizeLogMessage(message);\n\n if (!enableDataSanitization) {\n return {\n message: cleanMessage,\n meta: meta || {},\n };\n }\n\n return {\n message: sanitizeData(cleanMessage) as string,\n meta: sanitizeData(meta || {}) as Record<string, unknown>,\n };\n}\n\nexport function sanitizeForLoggingFormatter() {\n return {\n log: (obj) => {\n const message = (obj.msg || obj.message || '') as string;\n const { message: clean, meta } = sanitizeForLogging(message, obj as Record<string, unknown>);\n return { ...meta, msg: clean };\n },\n };\n}\n"],"names":["sanitizeData","sanitizeForLogging","sanitizeForLoggingFormatter","sanitizeLogMessage","SENSITIVE_PATTERNS","SENSITIVE_FIELDS","Set","isAlreadySanitized","value","includes","redactValue","length","repeat","substring","data","sanitized","pattern","replace","match","captured","redacted","Array","isArray","map","Object","entries","key","lowerKey","toLowerCase","has","message","maxLength","String","processedMessage","normalize","trim","meta","enableDataSanitization","cleanMessage","log","obj","msg","clean"],"mappings":";;;;;;;;;;;QA0LgBA;eAAAA;;QAmFAC;eAAAA;;QAgBAC;eAAAA;;QAhDAC;eAAAA;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;AA7OhB;;;;;;;;;;;;CAYC,GAED,wEAAwE,GACxE,IAAMC,qBAAqB;IACzB,mCAAmC;IACnC;IACA;IACA;IACA;IACA;IACA;IACA;IACA;IACA;IACA;IACA;IACA;IACA;IACA;IACA;IAEA,uCAAuC;IACvC;IACA;IACA;IACA;IACA;IACA;IACA;IACA;IACA;IACA;IACA;IACA;IAEA,4BAA4B;IAC5B;IACA;IACA;IACA;IACA;IACA;IACA;IACA;IACA;IACA;IAEA,wBAAwB;IACxB;IACA;IACA;IACA;IACA;IAEA,0BAA0B;IAC1B;IACA;IACA;IAEA,2BAA2B;IAC3B;IACA;IACA;IACA;IAEA,mEAAmE;IACnE;IACA;IACA;IAEA,yBAAyB;IACzB;CACD;AAED,kEAAkE,GAClE,IAAMC,mBAAmB,IAAIC,IAAI;IAC/B;IACA;IACA;IACA;IACA;IACA;IACA;IACA;IACA;IACA;IACA;IACA;IACA;IACA;IACA;IACA;IACA;IACA;IACA;IACA;IACA;IACA;IACA;IACA;IACA;IACA;IACA;IACA;IACA;IACA;IACA;IACA;IACA;IACA;IACA;IACA;IACA;IACA;IACA;IACA;IACA;IACA;IACA;IACA;IACA;IACA;IACA;IACA;IACA;IACA;IACA;IACA;IACA;IACA;IACA;IACA;IACA;IACA;IACA;IACA;IACA;IACA;IACA;IACA;IACA;IACA;IACA;IACA;IACA;IACA;IACA;IACA;IACA;IACA;IACA;IACA;IACA;IACA;IACA;CACD;AAED,SAASC,mBAAmBC,KAAa;IACvC,OAAOA,MAAMC,QAAQ,CAAC,WAAWD,MAAMC,QAAQ,CAAC,iBAAiBD,UAAU;AAC7E;AAEA,SAASE,YAAYF,KAAa;IAChC,IAAID,mBAAmBC,QAAQ;QAC7B,OAAOA;IACT;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"}
@@ -1 +1 @@
1
- {"version":3,"sources":["/Users/kevin/Dev/Projects/ai/mcp-z/oauth/oauth/src/schemas/index.ts"],"sourcesContent":["/**\n * OAuth-specific schemas and response builders\n *\n * Provider-agnostic schemas and utilities for building OAuth auth_required responses.\n * Individual OAuth packages (oauth-google, oauth-microsoft) wrap these with provider-specific defaults.\n */\n\nimport { z } from 'zod';\n\n// Re-export z for use in middleware (MCP requires zod)\nexport type { z };\n\n/**\n * Authentication required response type\n */\nexport interface AuthRequired {\n type: 'auth_required';\n provider: string;\n message: string;\n url: string;\n flow?: string;\n instructions: string;\n user_code?: string;\n expires_in?: number;\n accountId?: string;\n}\n\n/**\n * Zod schema for auth_required responses\n */\nexport const AuthRequiredSchema = z\n .object({\n type: z.literal('auth_required'),\n provider: z.string().describe('OAuth provider name (e.g., \"google\", \"microsoft\")'),\n message: z.string().describe('Human-readable message explaining why auth is needed'),\n url: z.string().url().describe('Authentication URL to open in browser'),\n flow: z.string().optional().describe('Authentication flow type (e.g., \"auth_url\", \"device_code\")'),\n instructions: z.string().describe('Clear instructions for the user'),\n user_code: z.string().optional().describe('Code user must enter at verification URL (device flows only)'),\n expires_in: z.number().optional().describe('Seconds until code expires (device flows only)'),\n accountId: z.string().optional().describe('Account identifier (email) that requires authentication'),\n })\n .describe('Authentication required with clear actionable instructions for user');\n"],"names":["AuthRequiredSchema","z","object","type","literal","provider","string","describe","message","url","flow","optional","instructions","user_code","expires_in","number","accountId"],"mappings":"AAAA;;;;;CAKC;;;;+BAyBYA;;;eAAAA;;;mBAvBK;AAuBX,IAAMA,qBAAqBC,MAAC,CAChCC,MAAM,CAAC;IACNC,MAAMF,MAAC,CAACG,OAAO,CAAC;IAChBC,UAAUJ,MAAC,CAACK,MAAM,GAAGC,QAAQ,CAAC;IAC9BC,SAASP,MAAC,CAACK,MAAM,GAAGC,QAAQ,CAAC;IAC7BE,KAAKR,MAAC,CAACK,MAAM,GAAGG,GAAG,GAAGF,QAAQ,CAAC;IAC/BG,MAAMT,MAAC,CAACK,MAAM,GAAGK,QAAQ,GAAGJ,QAAQ,CAAC;IACrCK,cAAcX,MAAC,CAACK,MAAM,GAAGC,QAAQ,CAAC;IAClCM,WAAWZ,MAAC,CAACK,MAAM,GAAGK,QAAQ,GAAGJ,QAAQ,CAAC;IAC1CO,YAAYb,MAAC,CAACc,MAAM,GAAGJ,QAAQ,GAAGJ,QAAQ,CAAC;IAC3CS,WAAWf,MAAC,CAACK,MAAM,GAAGK,QAAQ,GAAGJ,QAAQ,CAAC;AAC5C,GACCA,QAAQ,CAAC"}
1
+ {"version":3,"sources":["/Users/kevin/Dev/Projects/mcp-z/oauth/src/schemas/index.ts"],"sourcesContent":["/**\n * OAuth-specific schemas and response builders\n *\n * Provider-agnostic schemas and utilities for building OAuth auth_required responses.\n * Individual OAuth packages (oauth-google, oauth-microsoft) wrap these with provider-specific defaults.\n */\n\nimport { z } from 'zod';\n\n// Re-export z for use in middleware (MCP requires zod)\nexport type { z };\n\n/**\n * Authentication required response type\n */\nexport interface AuthRequired {\n type: 'auth_required';\n provider: string;\n message: string;\n url: string;\n flow?: string;\n instructions: string;\n user_code?: string;\n expires_in?: number;\n accountId?: string;\n}\n\n/**\n * Zod schema for auth_required responses\n */\nexport const AuthRequiredSchema = z\n .object({\n type: z.literal('auth_required'),\n provider: z.string().describe('OAuth provider name (e.g., \"google\", \"microsoft\")'),\n message: z.string().describe('Human-readable message explaining why auth is needed'),\n url: z.string().url().describe('Authentication URL to open in browser'),\n flow: z.string().optional().describe('Authentication flow type (e.g., \"auth_url\", \"device_code\")'),\n instructions: z.string().describe('Clear instructions for the user'),\n user_code: z.string().optional().describe('Code user must enter at verification URL (device flows only)'),\n expires_in: z.number().optional().describe('Seconds until code expires (device flows only)'),\n accountId: z.string().optional().describe('Account identifier (email) that requires authentication'),\n })\n .describe('Authentication required with clear actionable instructions for user');\n"],"names":["AuthRequiredSchema","z","object","type","literal","provider","string","describe","message","url","flow","optional","instructions","user_code","expires_in","number","accountId"],"mappings":"AAAA;;;;;CAKC;;;;+BAyBYA;;;eAAAA;;;mBAvBK;AAuBX,IAAMA,qBAAqBC,MAAC,CAChCC,MAAM,CAAC;IACNC,MAAMF,MAAC,CAACG,OAAO,CAAC;IAChBC,UAAUJ,MAAC,CAACK,MAAM,GAAGC,QAAQ,CAAC;IAC9BC,SAASP,MAAC,CAACK,MAAM,GAAGC,QAAQ,CAAC;IAC7BE,KAAKR,MAAC,CAACK,MAAM,GAAGG,GAAG,GAAGF,QAAQ,CAAC;IAC/BG,MAAMT,MAAC,CAACK,MAAM,GAAGK,QAAQ,GAAGJ,QAAQ,CAAC;IACrCK,cAAcX,MAAC,CAACK,MAAM,GAAGC,QAAQ,CAAC;IAClCM,WAAWZ,MAAC,CAACK,MAAM,GAAGK,QAAQ,GAAGJ,QAAQ,CAAC;IAC1CO,YAAYb,MAAC,CAACc,MAAM,GAAGJ,QAAQ,GAAGJ,QAAQ,CAAC;IAC3CS,WAAWf,MAAC,CAACK,MAAM,GAAGK,QAAQ,GAAGJ,QAAQ,CAAC;AAC5C,GACCA,QAAQ,CAAC"}
@@ -1 +1 @@
1
- {"version":3,"sources":["/Users/kevin/Dev/Projects/ai/mcp-z/oauth/oauth/src/session-auth.ts"],"sourcesContent":["/**\n * Session-based user authentication for multi-tenant deployments\n *\n * Extracts user ID from HTTP session cookies with HMAC signature verification.\n * Compatible with Express/Connect session middleware patterns.\n */\n\nimport { createHmac, timingSafeEqual } from 'crypto';\nimport type { SessionUserAuthConfig, UserAuthProvider } from './types.ts';\n\n/**\n * HTTP request interface (subset needed for session auth)\n */\ninterface HttpRequest {\n headers?: {\n cookie?: string;\n };\n}\n\n/**\n * Session cookie structure\n */\ninterface SessionData {\n userId: string;\n exp?: number; // Optional expiration timestamp (ms)\n}\n\n/**\n * Session-based user authentication provider\n *\n * Verifies signed session cookies and extracts user IDs.\n * Use for multi-tenant deployments where users authenticate via web sessions.\n *\n * @example\n * ```typescript\n * // Multi-tenant server setup with session-based user authentication\n * const userAuth = new SessionUserAuth({\n * sessionSecret: process.env.SESSION_SECRET!,\n * cookieName: 'app_session',\n * });\n *\n * // Create OAuth adapters with session-based user identification\n * const oauthAdapters = await createOAuthAdapters(\n * config.transport,\n * {\n * service: 'gmail',\n * clientId: process.env.GOOGLE_CLIENT_ID!,\n * clientSecret: process.env.GOOGLE_CLIENT_SECRET,\n * scope: GOOGLE_SCOPE,\n * auth: 'loopback-oauth',\n * headless: false,\n * redirectUri: undefined,\n * },\n * {\n * logger,\n * tokenStore,\n * userAuth, // Session-based user identification for multi-tenant deployments\n * }\n * );\n *\n * // Use auth middleware with tools\n * const { middleware: authMiddleware } = oauthAdapters;\n * const tools = toolFactories.map(f => f()).map(authMiddleware.withToolAuth);\n * ```\n */\nexport class SessionUserAuth implements UserAuthProvider {\n private readonly secret: string;\n private readonly cookieName: string;\n private readonly algorithm: 'sha256' | 'sha512';\n\n constructor(config: SessionUserAuthConfig) {\n if (!config.sessionSecret || config.sessionSecret.length < 32) {\n throw new Error('SessionUserAuth: sessionSecret must be at least 32 characters');\n }\n\n this.secret = config.sessionSecret;\n this.cookieName = config.cookieName ?? 'session';\n this.algorithm = config.algorithm ?? 'sha256';\n }\n\n /**\n * Extract and verify user ID from session cookie\n *\n * @param req - HTTP request object with cookie header\n * @returns User ID from verified session\n * @throws Error if session missing, invalid, or expired\n */\n async getUserId(req: unknown): Promise<string> {\n const httpReq = req as HttpRequest;\n\n const cookieHeader = httpReq.headers?.cookie;\n if (!cookieHeader) {\n throw new Error('SessionUserAuth: No cookie header found');\n }\n\n const sessionCookie = this.parseCookie(cookieHeader, this.cookieName);\n if (!sessionCookie) {\n throw new Error(`SessionUserAuth: Session cookie '${this.cookieName}' not found`);\n }\n\n // Format: base64(data).signature\n const parts = sessionCookie.split('.');\n if (parts.length !== 2) {\n throw new Error('SessionUserAuth: Invalid session format (expected data.signature)');\n }\n\n const [dataB64, signature] = parts as [string, string];\n\n const expectedSignature = this.sign(dataB64);\n if (!this.constantTimeCompare(signature, expectedSignature)) {\n throw new Error('SessionUserAuth: Invalid session signature');\n }\n\n let sessionData: SessionData;\n try {\n const dataJson = Buffer.from(dataB64, 'base64').toString('utf8');\n sessionData = JSON.parse(dataJson) as SessionData;\n } catch {\n throw new Error('SessionUserAuth: Invalid session data encoding');\n }\n\n if (sessionData.exp !== undefined && Date.now() > sessionData.exp) {\n throw new Error('SessionUserAuth: Session expired');\n }\n\n if (!sessionData.userId || typeof sessionData.userId !== 'string') {\n throw new Error('SessionUserAuth: Session missing userId field');\n }\n\n return sessionData.userId;\n }\n\n /**\n * Parse cookie from header string\n */\n private parseCookie(cookieHeader: string, name: string): string | null {\n const cookies = cookieHeader.split(';');\n for (const cookie of cookies) {\n const [key, ...valueParts] = cookie.trim().split('=');\n if (key === name) {\n return valueParts.join('='); // Handle values with = in them\n }\n }\n return null;\n }\n\n /**\n * Generate HMAC signature for session data\n */\n private sign(data: string): string {\n return createHmac(this.algorithm, this.secret).update(data).digest('hex');\n }\n\n /**\n * Constant-time string comparison to prevent timing attacks\n */\n private constantTimeCompare(a: string, b: string): boolean {\n if (a.length !== b.length) {\n return false;\n }\n\n const bufA = Buffer.from(a);\n const bufB = Buffer.from(b);\n\n return timingSafeEqual(bufA, bufB);\n }\n\n /**\n * Helper for creating session cookies (for testing/integration)\n *\n * @param userId - User ID to encode in session\n * @param expiresInMs - Optional expiration time in milliseconds from now\n * @returns Signed session cookie value\n */\n createSessionCookie(userId: string, expiresInMs?: number): string {\n const sessionData: SessionData = {\n userId,\n ...(expiresInMs !== undefined && { exp: Date.now() + expiresInMs }),\n };\n\n const dataJson = JSON.stringify(sessionData);\n const dataB64 = Buffer.from(dataJson).toString('base64');\n const signature = this.sign(dataB64);\n\n return `${dataB64}.${signature}`;\n }\n}\n"],"names":["SessionUserAuth","config","sessionSecret","length","Error","secret","cookieName","algorithm","getUserId","req","httpReq","cookieHeader","sessionCookie","parts","dataB64","signature","expectedSignature","sessionData","dataJson","headers","cookie","parseCookie","split","sign","constantTimeCompare","Buffer","from","toString","JSON","parse","exp","undefined","Date","now","userId","name","cookies","trim","key","valueParts","join","data","createHmac","update","digest","a","b","bufA","bufB","timingSafeEqual","createSessionCookie","expiresInMs","stringify"],"mappings":"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"}
1
+ {"version":3,"sources":["/Users/kevin/Dev/Projects/mcp-z/oauth/src/session-auth.ts"],"sourcesContent":["/**\n * Session-based user authentication for multi-tenant deployments\n *\n * Extracts user ID from HTTP session cookies with HMAC signature verification.\n * Compatible with Express/Connect session middleware patterns.\n */\n\nimport { createHmac, timingSafeEqual } from 'crypto';\nimport type { SessionUserAuthConfig, UserAuthProvider } from './types.ts';\n\n/**\n * HTTP request interface (subset needed for session auth)\n */\ninterface HttpRequest {\n headers?: {\n cookie?: string;\n };\n}\n\n/**\n * Session cookie structure\n */\ninterface SessionData {\n userId: string;\n exp?: number; // Optional expiration timestamp (ms)\n}\n\n/**\n * Session-based user authentication provider\n *\n * Verifies signed session cookies and extracts user IDs.\n * Use for multi-tenant deployments where users authenticate via web sessions.\n *\n * @example\n * ```typescript\n * // Multi-tenant server setup with session-based user authentication\n * const userAuth = new SessionUserAuth({\n * sessionSecret: process.env.SESSION_SECRET!,\n * cookieName: 'app_session',\n * });\n *\n * // Create OAuth adapters with session-based user identification\n * const oauthAdapters = await createOAuthAdapters(\n * config.transport,\n * {\n * service: 'gmail',\n * clientId: process.env.GOOGLE_CLIENT_ID!,\n * clientSecret: process.env.GOOGLE_CLIENT_SECRET,\n * scope: GOOGLE_SCOPE,\n * auth: 'loopback-oauth',\n * headless: false,\n * redirectUri: undefined,\n * },\n * {\n * logger,\n * tokenStore,\n * userAuth, // Session-based user identification for multi-tenant deployments\n * }\n * );\n *\n * // Use auth middleware with tools\n * const { middleware: authMiddleware } = oauthAdapters;\n * const tools = toolFactories.map(f => f()).map(authMiddleware.withToolAuth);\n * ```\n */\nexport class SessionUserAuth implements UserAuthProvider {\n private readonly secret: string;\n private readonly cookieName: string;\n private readonly algorithm: 'sha256' | 'sha512';\n\n constructor(config: SessionUserAuthConfig) {\n if (!config.sessionSecret || config.sessionSecret.length < 32) {\n throw new Error('SessionUserAuth: sessionSecret must be at least 32 characters');\n }\n\n this.secret = config.sessionSecret;\n this.cookieName = config.cookieName ?? 'session';\n this.algorithm = config.algorithm ?? 'sha256';\n }\n\n /**\n * Extract and verify user ID from session cookie\n *\n * @param req - HTTP request object with cookie header\n * @returns User ID from verified session\n * @throws Error if session missing, invalid, or expired\n */\n async getUserId(req: unknown): Promise<string> {\n const httpReq = req as HttpRequest;\n\n const cookieHeader = httpReq.headers?.cookie;\n if (!cookieHeader) {\n throw new Error('SessionUserAuth: No cookie header found');\n }\n\n const sessionCookie = this.parseCookie(cookieHeader, this.cookieName);\n if (!sessionCookie) {\n throw new Error(`SessionUserAuth: Session cookie '${this.cookieName}' not found`);\n }\n\n // Format: base64(data).signature\n const parts = sessionCookie.split('.');\n if (parts.length !== 2) {\n throw new Error('SessionUserAuth: Invalid session format (expected data.signature)');\n }\n\n const [dataB64, signature] = parts as [string, string];\n\n const expectedSignature = this.sign(dataB64);\n if (!this.constantTimeCompare(signature, expectedSignature)) {\n throw new Error('SessionUserAuth: Invalid session signature');\n }\n\n let sessionData: SessionData;\n try {\n const dataJson = Buffer.from(dataB64, 'base64').toString('utf8');\n sessionData = JSON.parse(dataJson) as SessionData;\n } catch {\n throw new Error('SessionUserAuth: Invalid session data encoding');\n }\n\n if (sessionData.exp !== undefined && Date.now() > sessionData.exp) {\n throw new Error('SessionUserAuth: Session expired');\n }\n\n if (!sessionData.userId || typeof sessionData.userId !== 'string') {\n throw new Error('SessionUserAuth: Session missing userId field');\n }\n\n return sessionData.userId;\n }\n\n /**\n * Parse cookie from header string\n */\n private parseCookie(cookieHeader: string, name: string): string | null {\n const cookies = cookieHeader.split(';');\n for (const cookie of cookies) {\n const [key, ...valueParts] = cookie.trim().split('=');\n if (key === name) {\n return valueParts.join('='); // Handle values with = in them\n }\n }\n return null;\n }\n\n /**\n * Generate HMAC signature for session data\n */\n private sign(data: string): string {\n return createHmac(this.algorithm, this.secret).update(data).digest('hex');\n }\n\n /**\n * Constant-time string comparison to prevent timing attacks\n */\n private constantTimeCompare(a: string, b: string): boolean {\n if (a.length !== b.length) {\n return false;\n }\n\n const bufA = Buffer.from(a);\n const bufB = Buffer.from(b);\n\n return timingSafeEqual(bufA, bufB);\n }\n\n /**\n * Helper for creating session cookies (for testing/integration)\n *\n * @param userId - User ID to encode in session\n * @param expiresInMs - Optional expiration time in milliseconds from now\n * @returns Signed session cookie value\n */\n createSessionCookie(userId: string, expiresInMs?: number): string {\n const sessionData: SessionData = {\n userId,\n ...(expiresInMs !== undefined && { exp: Date.now() + expiresInMs }),\n };\n\n const dataJson = JSON.stringify(sessionData);\n const dataB64 = Buffer.from(dataJson).toString('base64');\n const signature = this.sign(dataB64);\n\n return `${dataB64}.${signature}`;\n }\n}\n"],"names":["SessionUserAuth","config","sessionSecret","length","Error","secret","cookieName","algorithm","getUserId","req","httpReq","cookieHeader","sessionCookie","parts","dataB64","signature","expectedSignature","sessionData","dataJson","headers","cookie","parseCookie","split","sign","constantTimeCompare","Buffer","from","toString","JSON","parse","exp","undefined","Date","now","userId","name","cookies","trim","key","valueParts","join","data","createHmac","update","digest","a","b","bufA","bufB","timingSafeEqual","createSessionCookie","expiresInMs","stringify"],"mappings":"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"}
@@ -1 +1 @@
1
- {"version":3,"sources":["/Users/kevin/Dev/Projects/ai/mcp-z/oauth/oauth/src/templates.ts"],"sourcesContent":["/**\n * HTML templates for OAuth callback pages\n *\n * These templates are shown in the browser after OAuth authorization\n * for loopback OAuth flows (RFC 8252).\n */\n\n/**\n * HTML template for successful OAuth authorization\n */\nexport function getSuccessTemplate(): string {\n return `\n<!DOCTYPE html>\n<html>\n<head>\n <meta charset=\"utf-8\">\n <title>Authorization Successful</title>\n <style>\n body {\n font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, Oxygen, Ubuntu, Cantarell, sans-serif;\n display: flex;\n justify-content: center;\n align-items: center;\n height: 100vh;\n margin: 0;\n background: linear-gradient(135deg, #e8ebf7 0%, #ede9f2 100%);\n }\n .container {\n background: white;\n padding: 3rem;\n border-radius: 1rem;\n box-shadow: 0 20px 60px rgba(0,0,0,0.3);\n text-align: center;\n max-width: 400px;\n }\n h1 {\n color: #2d3748;\n margin-bottom: 1rem;\n font-size: 1.875rem;\n }\n p {\n color: #718096;\n font-size: 1.125rem;\n margin-bottom: 1.5rem;\n }\n .icon {\n font-size: 4rem;\n margin-bottom: 1rem;\n }\n </style>\n</head>\n<body>\n <div class=\"container\">\n <div class=\"icon\">✅</div>\n <h1>Authorization Successful!</h1>\n <p>You can close this window and return to your terminal.</p>\n </div>\n <script>\n // Auto-close after 3 seconds\n setTimeout(() => {\n window.close();\n }, 3000);\n </script>\n</body>\n</html>\n `.trim();\n}\n\n/**\n * HTML template for OAuth error\n */\nexport function getErrorTemplate(error: string): string {\n return `\n<!DOCTYPE html>\n<html>\n<head>\n <meta charset=\"utf-8\">\n <title>Authorization Failed</title>\n <style>\n body {\n font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, Oxygen, Ubuntu, Cantarell, sans-serif;\n display: flex;\n justify-content: center;\n align-items: center;\n height: 100vh;\n margin: 0;\n background: linear-gradient(135deg, #fce9f7 0%, #faebed 100%);\n }\n .container {\n background: white;\n padding: 3rem;\n border-radius: 1rem;\n box-shadow: 0 20px 60px rgba(0,0,0,0.3);\n text-align: center;\n max-width: 400px;\n }\n h1 {\n color: #2d3748;\n margin-bottom: 1rem;\n font-size: 1.875rem;\n }\n p {\n color: #718096;\n font-size: 1.125rem;\n margin-bottom: 1.5rem;\n }\n .error {\n background: #fed7d7;\n color: #9b2c2c;\n padding: 0.75rem;\n border-radius: 0.5rem;\n margin-top: 1rem;\n font-family: monospace;\n font-size: 0.875rem;\n }\n .icon {\n font-size: 4rem;\n margin-bottom: 1rem;\n }\n </style>\n</head>\n<body>\n <div class=\"container\">\n <div class=\"icon\">❌</div>\n <h1>Authorization Failed</h1>\n <p>Please try again from your terminal.</p>\n <div class=\"error\">${escapeHtml(error)}</div>\n </div>\n</body>\n</html>\n `.trim();\n}\n\n/**\n * Escape HTML special characters to prevent XSS\n */\nexport function escapeHtml(unsafe: string): string {\n return unsafe.replace(/&/g, '&amp;').replace(/</g, '&lt;').replace(/>/g, '&gt;').replace(/\"/g, '&quot;').replace(/'/g, '&#039;');\n}\n"],"names":["escapeHtml","getErrorTemplate","getSuccessTemplate","trim","error","unsafe","replace"],"mappings":"AAAA;;;;;CAKC,GAED;;CAEC;;;;;;;;;;;QA+HeA;eAAAA;;QAjEAC;eAAAA;;QA7DAC;eAAAA;;;AAAT,SAASA;IACd,OAAO,yuCAsDLC,IAAI;AACR;AAKO,SAASF,iBAAiBG,KAAa;IAC5C,OAAO,AAAC,gyCAsDiC,OAAlBJ,WAAWI,QAAO,0CAIvCD,IAAI;AACR;AAKO,SAASH,WAAWK,MAAc;IACvC,OAAOA,OAAOC,OAAO,CAAC,MAAM,SAASA,OAAO,CAAC,MAAM,QAAQA,OAAO,CAAC,MAAM,QAAQA,OAAO,CAAC,MAAM,UAAUA,OAAO,CAAC,MAAM;AACzH"}
1
+ {"version":3,"sources":["/Users/kevin/Dev/Projects/mcp-z/oauth/src/templates.ts"],"sourcesContent":["/**\n * HTML templates for OAuth callback pages\n *\n * These templates are shown in the browser after OAuth authorization\n * for loopback OAuth flows (RFC 8252).\n */\n\n/**\n * HTML template for successful OAuth authorization\n */\nexport function getSuccessTemplate(): string {\n return `\n<!DOCTYPE html>\n<html>\n<head>\n <meta charset=\"utf-8\">\n <title>Authorization Successful</title>\n <style>\n body {\n font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, Oxygen, Ubuntu, Cantarell, sans-serif;\n display: flex;\n justify-content: center;\n align-items: center;\n height: 100vh;\n margin: 0;\n background: linear-gradient(135deg, #e8ebf7 0%, #ede9f2 100%);\n }\n .container {\n background: white;\n padding: 3rem;\n border-radius: 1rem;\n box-shadow: 0 20px 60px rgba(0,0,0,0.3);\n text-align: center;\n max-width: 400px;\n }\n h1 {\n color: #2d3748;\n margin-bottom: 1rem;\n font-size: 1.875rem;\n }\n p {\n color: #718096;\n font-size: 1.125rem;\n margin-bottom: 1.5rem;\n }\n .icon {\n font-size: 4rem;\n margin-bottom: 1rem;\n }\n </style>\n</head>\n<body>\n <div class=\"container\">\n <div class=\"icon\">✅</div>\n <h1>Authorization Successful!</h1>\n <p>You can close this window and return to your terminal.</p>\n </div>\n <script>\n // Auto-close after 3 seconds\n setTimeout(() => {\n window.close();\n }, 3000);\n </script>\n</body>\n</html>\n `.trim();\n}\n\n/**\n * HTML template for OAuth error\n */\nexport function getErrorTemplate(error: string): string {\n return `\n<!DOCTYPE html>\n<html>\n<head>\n <meta charset=\"utf-8\">\n <title>Authorization Failed</title>\n <style>\n body {\n font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, Oxygen, Ubuntu, Cantarell, sans-serif;\n display: flex;\n justify-content: center;\n align-items: center;\n height: 100vh;\n margin: 0;\n background: linear-gradient(135deg, #fce9f7 0%, #faebed 100%);\n }\n .container {\n background: white;\n padding: 3rem;\n border-radius: 1rem;\n box-shadow: 0 20px 60px rgba(0,0,0,0.3);\n text-align: center;\n max-width: 400px;\n }\n h1 {\n color: #2d3748;\n margin-bottom: 1rem;\n font-size: 1.875rem;\n }\n p {\n color: #718096;\n font-size: 1.125rem;\n margin-bottom: 1.5rem;\n }\n .error {\n background: #fed7d7;\n color: #9b2c2c;\n padding: 0.75rem;\n border-radius: 0.5rem;\n margin-top: 1rem;\n font-family: monospace;\n font-size: 0.875rem;\n }\n .icon {\n font-size: 4rem;\n margin-bottom: 1rem;\n }\n </style>\n</head>\n<body>\n <div class=\"container\">\n <div class=\"icon\">❌</div>\n <h1>Authorization Failed</h1>\n <p>Please try again from your terminal.</p>\n <div class=\"error\">${escapeHtml(error)}</div>\n </div>\n</body>\n</html>\n `.trim();\n}\n\n/**\n * Escape HTML special characters to prevent XSS\n */\nexport function escapeHtml(unsafe: string): string {\n return unsafe.replace(/&/g, '&amp;').replace(/</g, '&lt;').replace(/>/g, '&gt;').replace(/\"/g, '&quot;').replace(/'/g, '&#039;');\n}\n"],"names":["escapeHtml","getErrorTemplate","getSuccessTemplate","trim","error","unsafe","replace"],"mappings":"AAAA;;;;;CAKC,GAED;;CAEC;;;;;;;;;;;QA+HeA;eAAAA;;QAjEAC;eAAAA;;QA7DAC;eAAAA;;;AAAT,SAASA;IACd,OAAO,yuCAsDLC,IAAI;AACR;AAKO,SAASF,iBAAiBG,KAAa;IAC5C,OAAO,AAAC,gyCAsDiC,OAAlBJ,WAAWI,QAAO,0CAIvCD,IAAI;AACR;AAKO,SAASH,WAAWK,MAAc;IACvC,OAAOA,OAAOC,OAAO,CAAC,MAAM,SAASA,OAAO,CAAC,MAAM,QAAQA,OAAO,CAAC,MAAM,QAAQA,OAAO,CAAC,MAAM,UAAUA,OAAO,CAAC,MAAM;AACzH"}
@@ -126,10 +126,11 @@ export declare class RequiresAuthenticationError extends AccountManagerError {
126
126
  accountId: string | undefined;
127
127
  constructor(service: string, accountId?: string);
128
128
  }
129
- export interface AuthEmailProvider {
129
+ export interface AccountAuthProvider {
130
+ getAccessToken(accountId?: string): Promise<string>;
130
131
  getUserEmail(accountId?: string): Promise<string>;
131
- authenticateNewAccount?(): Promise<string>;
132
132
  }
133
+ export type AuthEmailProvider = AccountAuthProvider;
133
134
  export interface UserAuthProvider {
134
135
  getUserId(req: unknown): Promise<string>;
135
136
  }
@@ -126,10 +126,11 @@ export declare class RequiresAuthenticationError extends AccountManagerError {
126
126
  accountId: string | undefined;
127
127
  constructor(service: string, accountId?: string);
128
128
  }
129
- export interface AuthEmailProvider {
129
+ export interface AccountAuthProvider {
130
+ getAccessToken(accountId?: string): Promise<string>;
130
131
  getUserEmail(accountId?: string): Promise<string>;
131
- authenticateNewAccount?(): Promise<string>;
132
132
  }
133
+ export type AuthEmailProvider = AccountAuthProvider;
133
134
  export interface UserAuthProvider {
134
135
  getUserId(req: unknown): Promise<string>;
135
136
  }
@@ -1 +1 @@
1
- {"version":3,"sources":["/Users/kevin/Dev/Projects/ai/mcp-z/oauth/oauth/src/types.ts"],"sourcesContent":["/**\n * Type definitions for multi-account management and OAuth integration\n */\n\nimport type { McpServer } from '@modelcontextprotocol/sdk/server/mcp.js';\nimport type { AnySchema, ZodRawShapeCompat } from '@modelcontextprotocol/sdk/server/zod-compat.js';\nimport type { RequestHandlerExtra } from '@modelcontextprotocol/sdk/shared/protocol.js';\nimport type { CallToolResult, GetPromptResult, ServerNotification, ServerRequest, ToolAnnotations } from '@modelcontextprotocol/sdk/types.js';\n\nexport type Logger = Pick<Console, 'info' | 'error' | 'warn' | 'debug'>;\n\nexport interface AccountInfo {\n email: string;\n alias?: string;\n addedAt: string;\n lastUsed?: string;\n metadata?: {\n name?: string;\n picture?: string;\n [key: string]: unknown;\n };\n}\n\n/**\n * MCP tool module definition with configuration and handler function.\n *\n * Represents a registered tool in the Model Context Protocol server that can be\n * invoked by MCP clients. Tools are the primary mechanism for executing operations\n * in response to client requests.\n *\n * @property name - Unique tool identifier (e.g., \"gmail-message-send\", \"sheets-values-get\")\n * @property config - Tool configuration including description and schemas\n * @property config.description - Human-readable description shown to MCP clients\n * @property config.inputSchema - Zod schema defining tool arguments (JSON-serializable)\n * @property config.outputSchema - Zod schema defining tool response structure\n * @property handler - Async function that executes the tool operation\n *\n * @remarks\n * This is the runtime representation of an MCP tool after registration. The handler\n * receives JSON-serializable arguments validated against inputSchema and returns\n * a CallToolResult validated against outputSchema.\n *\n * Tools are typically created using tool factory functions and registered with the\n * MCP server during initialization.\n *\n * @example\n * ```typescript\n * const tool: McpTool = {\n * name: \"gmail-message-send\",\n * config: {\n * description: \"Send an email message\",\n * inputSchema: { to: { type: \"string\" }, subject: { type: \"string\" } },\n * outputSchema: { result: { type: \"object\" } }\n * },\n * handler: async (args, context) => {\n * // Implementation\n * return { content: [{ type: \"text\", text: \"Message sent\" }] };\n * }\n * };\n * ```\n *\n * @see {@link McpPrompt} for prompt module definition\n */\nexport interface McpTool {\n name: string;\n config: {\n description: string;\n inputSchema: Record<string, unknown>;\n outputSchema: Record<string, unknown>;\n };\n handler: (args: unknown, context?: unknown) => Promise<CallToolResult>;\n}\n\n/**\n * MCP prompt module definition with configuration and handler function.\n *\n * Represents a registered prompt template in the Model Context Protocol server that\n * can be retrieved and rendered by MCP clients. Prompts provide reusable templates\n * for common interaction patterns.\n *\n * @property name - Unique prompt identifier (e.g., \"draft-email\", \"summarize-thread\")\n * @property config - Prompt configuration (schema and metadata are prompt-specific)\n * @property handler - Async function that generates the prompt content\n *\n * @remarks\n * This is the runtime representation of an MCP prompt after registration. Unlike\n * {@link McpTool} which executes operations, prompts generate templated content\n * that clients can use to structure interactions.\n *\n * The handler receives optional arguments and returns a GetPromptResult containing\n * the rendered prompt messages.\n *\n * @example\n * ```typescript\n * const prompt: McpPrompt = {\n * name: \"draft-email\",\n * config: {\n * description: \"Generate email draft from key points\",\n * arguments: [{ name: \"points\", description: \"Key points to include\" }]\n * },\n * handler: async (args) => {\n * const points = args?.points || [];\n * return {\n * messages: [{\n * role: \"user\",\n * content: { type: \"text\", text: `Draft email covering: ${points.join(\", \")}` }\n * }]\n * };\n * }\n * };\n * ```\n *\n * @see {@link McpTool} for tool module definition\n */\nexport interface McpPrompt {\n name: string;\n config: unknown;\n handler: (args: unknown) => Promise<GetPromptResult>;\n}\n\nexport class AccountManagerError extends Error {\n public code: string;\n public retryable: boolean;\n\n constructor(message: string, code: string, retryable = false) {\n super(message);\n this.name = 'AccountManagerError';\n this.code = code;\n this.retryable = retryable;\n }\n}\n\nexport class AccountNotFoundError extends AccountManagerError {\n constructor(accountRef: string) {\n super(`Account '${accountRef}' not found`, 'ACCOUNT_NOT_FOUND', false);\n }\n}\n\nexport class ConfigurationError extends AccountManagerError {\n constructor(message: string) {\n super(`Configuration error: ${message}`, 'CONFIGURATION_ERROR', false);\n }\n}\n\nexport class RequiresAuthenticationError extends AccountManagerError {\n public accountId: string | undefined;\n\n constructor(service: string, accountId?: string) {\n const message = accountId ? `No account found for ${service} (account: ${accountId}). Use account-add to connect one.` : `No account found for ${service}. Use account-add to connect one.`;\n super(message, 'REQUIRES_AUTHENTICATION', false);\n this.accountId = accountId;\n }\n}\n\nexport interface AuthEmailProvider {\n getUserEmail(accountId?: string): Promise<string>;\n authenticateNewAccount?(): Promise<string>;\n}\n\nexport interface UserAuthProvider {\n getUserId(req: unknown): Promise<string>; // Throws if auth invalid\n}\n\nexport interface JWTUserAuthConfig {\n secret?: string; // HS256 - MUST be at least 32 characters\n publicKey?: string | object; // RS256/ES256 - PEM string, JWK object, or JWKS URL\n jwksUrl?: string; // Alternative to publicKey\n issuer?: string | string[];\n audience?: string | string[];\n userIdClaim?: string; // Default: 'sub'\n algorithms?: string[]; // Default: auto-detect\n clockTolerance?: number; // Default: 0\n}\n\nexport interface SessionUserAuthConfig {\n sessionSecret: string; // MUST be at least 32 characters\n cookieName?: string; // Default: 'session'\n algorithm?: 'sha256' | 'sha512'; // Default: 'sha256'\n}\n\nexport interface Credentials {\n accessToken: string;\n expiresAt?: number;\n refreshToken?: string;\n scope?: string;\n tokenType?: string;\n idToken?: string;\n}\n\nexport type AuthFlowDescriptor =\n | { kind: 'credentials'; connection?: string; provider?: string; credentials: Credentials }\n | { kind: 'auth_url'; connection?: string; provider?: string; url: string; txn?: string; state?: string; codeVerifier?: string; poll?: { statusUrl: string; interval?: number }; hint?: string }\n | { kind: 'device_code'; connection?: string; provider?: string; txn?: string; device: { userCode: string; verificationUri: string; verificationUriComplete?: string; expiresIn: number; interval: number }; poll?: { statusUrl: string; interval?: number }; hint?: string }\n | { kind: 'error'; error: string; code?: number };\n\nexport class AuthRequiredError extends Error {\n public descriptor: AuthFlowDescriptor;\n\n constructor(descriptor: AuthFlowDescriptor, message?: string) {\n super(message || `Authentication required: ${descriptor.kind}`);\n this.name = 'AuthRequiredError';\n this.descriptor = descriptor;\n }\n}\n\nexport interface CachedToken {\n accessToken: string;\n refreshToken?: string;\n expiresAt?: number;\n scope?: string;\n}\n\n/**\n * Tool config signature - explicit structural type mirroring SDK registerTool config\n *\n * Uses explicit structure instead of Parameters<> extraction to avoid TypeScript inference\n * collapse to 'never' when using ToolModule[] arrays. The deep conditional types from\n * Parameters<> cannot be unified across array elements.\n *\n * Validated against SDK signature for compatibility - compile errors if SDK changes.\n *\n * NOTE: This type is duplicated in @mcp-z/server for architectural independence.\n * Keep these definitions synchronized manually when updating.\n */\nexport type ToolConfig = {\n title?: string;\n description?: string;\n inputSchema?: ZodRawShapeCompat | AnySchema;\n outputSchema?: ZodRawShapeCompat | AnySchema;\n annotations?: ToolAnnotations;\n _meta?: Record<string, unknown>;\n};\n\n// Compile-time validation that ToolConfig is compatible with SDK\ntype _ValidateToolConfigAssignable = ToolConfig extends Parameters<McpServer['registerTool']>[1] ? true : never;\ntype _ValidateToolConfigReceivable = Parameters<McpServer['registerTool']>[1] extends ToolConfig ? true : never;\n\n/**\n * Tool handler signature with generic support for middleware.\n *\n * @template TArgs - Tool arguments type (default: unknown for SDK compatibility)\n * @template TExtra - Request handler extra type (default: RequestHandlerExtra from SDK)\n *\n * Defaults provide SDK-extracted types for compatibility with MCP SDK.\n * Generic parameters enable type-safe middleware transformation.\n *\n * NOTE: This interface is duplicated in @mcp-z/server for architectural independence.\n * Keep these definitions synchronized manually when updating.\n */\nexport type ToolHandler<TArgs = unknown, TExtra = RequestHandlerExtra<ServerRequest, ServerNotification>> = (args: TArgs, extra: TExtra) => Promise<CallToolResult>;\n\n/**\n * Tool module interface with bounded generics.\n *\n * @template TConfig - Tool config type (default: SDK ToolConfig)\n * @template THandler - Handler function type (default: SDK ToolHandler)\n *\n * Use without generics for SDK-typed tools:\n * - Business tool factories: `ToolModule`\n * - Tool registration: `ToolModule[]`\n *\n * Use with generics for middleware transformation:\n * - Auth middleware: `ToolModule<ToolConfig, ToolHandler<TArgs, EnrichedExtra>>`\n *\n * The bounds ensure compatibility with SDK registration.\n *\n * NOTE: This interface is duplicated in @mcp-z/server for architectural independence.\n * Keep these definitions synchronized manually when updating.\n *\n * @see {@link ToolHandler} for handler function signature\n * @see {@link AuthMiddlewareWrapper} for middleware wrapper pattern\n */\nexport interface ToolModule<TConfig = ToolConfig, THandler = unknown> {\n name: string;\n config: TConfig;\n handler: THandler;\n}\n\n/**\n * Middleware wrapper that enriches tool modules with authentication context.\n *\n * Wraps plain tool modules to inject authentication, logging, and request metadata.\n * The wrapper pattern allows separation of business logic from cross-cutting concerns.\n *\n * @template TArgs - Tool arguments type (inferred from tool module)\n * @template TExtra - Enriched extra type with auth context and logger\n *\n * @param toolModule - Plain tool module to wrap with auth middleware\n * @returns Wrapped tool module with enriched handler signature\n *\n * @remarks\n * Auth middleware wrappers typically:\n * - Extract auth context from MCP request or OAuth provider\n * - Inject logger instance for structured logging\n * - Handle authentication errors with proper MCP error responses\n * - Preserve tool configuration and metadata\n *\n * @example\n * ```typescript\n * // Actual usage pattern from OAuth providers (LoopbackOAuthProvider, ServiceAccountProvider, DcrOAuthProvider)\n * const provider = new LoopbackOAuthProvider({ service: 'gmail', ... });\n * const authMiddleware = provider.authMiddleware();\n *\n * // Apply middleware to tools (handlers receive enriched extra with authContext)\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 enriched extra with guaranteed authContext\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 * // ... business logic with authenticated context\n * }\n * ```\n *\n * @see {@link ToolModule} for base tool interface\n * @see {@link ToolHandler} for handler function signature\n */\nexport type AuthMiddlewareWrapper<TArgs = unknown, TExtra = RequestHandlerExtra<ServerRequest, ServerNotification>> = (toolModule: ToolModule) => ToolModule<ToolConfig, ToolHandler<TArgs, TExtra>>;\n\n/**\n * Base interface for stateful OAuth adapters (LoopbackOAuthProvider pattern)\n *\n * Stateful adapters manage token storage, refresh, and multi-account state.\n * Used for local development, test setup, and CI/CD workflows.\n *\n * Key characteristics:\n * - Token storage and retrieval via tokenStore\n * - Automatic token refresh with provider\n * - Interactive OAuth flows (browser, ephemeral server)\n * - Multi-account management\n *\n * Parameter usage:\n * - accountId: Account identifier (email address for token storage)\n */\nexport interface OAuth2TokenStorageProvider {\n /**\n * Get access token for the specified account.\n * If token is expired, automatically refreshes it.\n * If token is missing, triggers OAuth flow (interactive) or throws AuthRequired (headless).\n *\n * @param accountId - Account identifier for multi-account support\n * @returns Access token string\n */\n getAccessToken(accountId?: string): Promise<string>;\n\n /**\n * Get email address for the specified account.\n * Used during account registration to verify identity with provider.\n *\n * @param accountId - Account identifier\n * @returns Email address from provider verification\n */\n getUserEmail(accountId?: string): Promise<string>;\n}\n"],"names":["AccountManagerError","AccountNotFoundError","AuthRequiredError","ConfigurationError","RequiresAuthenticationError","message","code","retryable","name","Error","accountRef","service","accountId","descriptor","kind"],"mappings":"AAAA;;CAEC;;;;;;;;;;;QAsHYA;eAAAA;;QAYAC;eAAAA;;QA+DAC;eAAAA;;QAzDAC;eAAAA;;QAMAC;eAAAA;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;AAxBN,IAAA,AAAMJ,oCAAN;;cAAMA;aAAAA,oBAICK,OAAe,EAAEC,IAAY;YAAEC,YAAAA,iEAAY;gCAJ5CP;;gBAKT,kBALSA;YAKHK;;QACN,MAAKG,IAAI,GAAG;QACZ,MAAKF,IAAI,GAAGA;QACZ,MAAKC,SAAS,GAAGA;;;WARRP;qBAA4BS;AAYlC,IAAA,AAAMR,qCAAN;;cAAMA;aAAAA,qBACCS,UAAkB;gCADnBT;QAET,OAAA,kBAFSA;YAEF,YAAsB,OAAXS,YAAW;YAAc;YAAqB;;;WAFvDT;EAA6BD;AAMnC,IAAA,AAAMG,mCAAN;;cAAMA;aAAAA,mBACCE,OAAe;gCADhBF;QAET,OAAA,kBAFSA;YAEF,wBAA+B,OAARE;YAAW;YAAuB;;;WAFvDF;EAA2BH;AAMjC,IAAA,AAAMI,4CAAN;;cAAMA;aAAAA,4BAGCO,OAAe,EAAEC,SAAkB;gCAHpCR;;QAIT,IAAMC,UAAUO,YAAY,AAAC,wBAA4CA,OAArBD,SAAQ,eAAuB,OAAVC,WAAU,wCAAsC,AAAC,wBAA+B,OAARD,SAAQ;gBACzJ,kBALSP;YAKHC;YAAS;YAA2B;;QAC1C,MAAKO,SAAS,GAAGA;;;WANRR;EAAoCJ;AAmD1C,IAAA,AAAME,kCAAN;;cAAMA;aAAAA,kBAGCW,UAA8B,EAAER,OAAgB;gCAHjDH;;gBAIT,kBAJSA;YAIHG,WAAW,AAAC,4BAA2C,OAAhBQ,WAAWC,IAAI;;QAC5D,MAAKN,IAAI,GAAG;QACZ,MAAKK,UAAU,GAAGA;;;WANTX;qBAA0BO"}
1
+ {"version":3,"sources":["/Users/kevin/Dev/Projects/mcp-z/oauth/src/types.ts"],"sourcesContent":["/**\n * Type definitions for multi-account management and OAuth integration\n */\n\nimport type { McpServer } from '@modelcontextprotocol/sdk/server/mcp.js';\nimport type { AnySchema, ZodRawShapeCompat } from '@modelcontextprotocol/sdk/server/zod-compat.js';\nimport type { RequestHandlerExtra } from '@modelcontextprotocol/sdk/shared/protocol.js';\nimport type { CallToolResult, GetPromptResult, ServerNotification, ServerRequest, ToolAnnotations } from '@modelcontextprotocol/sdk/types.js';\n\nexport type Logger = Pick<Console, 'info' | 'error' | 'warn' | 'debug'>;\n\nexport interface AccountInfo {\n email: string;\n alias?: string;\n addedAt: string;\n lastUsed?: string;\n metadata?: {\n name?: string;\n picture?: string;\n [key: string]: unknown;\n };\n}\n\n/**\n * MCP tool module definition with configuration and handler function.\n *\n * Represents a registered tool in the Model Context Protocol server that can be\n * invoked by MCP clients. Tools are the primary mechanism for executing operations\n * in response to client requests.\n *\n * @property name - Unique tool identifier (e.g., \"gmail-message-send\", \"sheets-values-get\")\n * @property config - Tool configuration including description and schemas\n * @property config.description - Human-readable description shown to MCP clients\n * @property config.inputSchema - Zod schema defining tool arguments (JSON-serializable)\n * @property config.outputSchema - Zod schema defining tool response structure\n * @property handler - Async function that executes the tool operation\n *\n * @remarks\n * This is the runtime representation of an MCP tool after registration. The handler\n * receives JSON-serializable arguments validated against inputSchema and returns\n * a CallToolResult validated against outputSchema.\n *\n * Tools are typically created using tool factory functions and registered with the\n * MCP server during initialization.\n *\n * @example\n * ```typescript\n * const tool: McpTool = {\n * name: \"gmail-message-send\",\n * config: {\n * description: \"Send an email message\",\n * inputSchema: { to: { type: \"string\" }, subject: { type: \"string\" } },\n * outputSchema: { result: { type: \"object\" } }\n * },\n * handler: async (args, context) => {\n * // Implementation\n * return { content: [{ type: \"text\", text: \"Message sent\" }] };\n * }\n * };\n * ```\n *\n * @see {@link McpPrompt} for prompt module definition\n */\nexport interface McpTool {\n name: string;\n config: {\n description: string;\n inputSchema: Record<string, unknown>;\n outputSchema: Record<string, unknown>;\n };\n handler: (args: unknown, context?: unknown) => Promise<CallToolResult>;\n}\n\n/**\n * MCP prompt module definition with configuration and handler function.\n *\n * Represents a registered prompt template in the Model Context Protocol server that\n * can be retrieved and rendered by MCP clients. Prompts provide reusable templates\n * for common interaction patterns.\n *\n * @property name - Unique prompt identifier (e.g., \"draft-email\", \"summarize-thread\")\n * @property config - Prompt configuration (schema and metadata are prompt-specific)\n * @property handler - Async function that generates the prompt content\n *\n * @remarks\n * This is the runtime representation of an MCP prompt after registration. Unlike\n * {@link McpTool} which executes operations, prompts generate templated content\n * that clients can use to structure interactions.\n *\n * The handler receives optional arguments and returns a GetPromptResult containing\n * the rendered prompt messages.\n *\n * @example\n * ```typescript\n * const prompt: McpPrompt = {\n * name: \"draft-email\",\n * config: {\n * description: \"Generate email draft from key points\",\n * arguments: [{ name: \"points\", description: \"Key points to include\" }]\n * },\n * handler: async (args) => {\n * const points = args?.points || [];\n * return {\n * messages: [{\n * role: \"user\",\n * content: { type: \"text\", text: `Draft email covering: ${points.join(\", \")}` }\n * }]\n * };\n * }\n * };\n * ```\n *\n * @see {@link McpTool} for tool module definition\n */\nexport interface McpPrompt {\n name: string;\n config: unknown;\n handler: (args: unknown) => Promise<GetPromptResult>;\n}\n\nexport class AccountManagerError extends Error {\n public code: string;\n public retryable: boolean;\n\n constructor(message: string, code: string, retryable = false) {\n super(message);\n this.name = 'AccountManagerError';\n this.code = code;\n this.retryable = retryable;\n }\n}\n\nexport class AccountNotFoundError extends AccountManagerError {\n constructor(accountRef: string) {\n super(`Account '${accountRef}' not found`, 'ACCOUNT_NOT_FOUND', false);\n }\n}\n\nexport class ConfigurationError extends AccountManagerError {\n constructor(message: string) {\n super(`Configuration error: ${message}`, 'CONFIGURATION_ERROR', false);\n }\n}\n\nexport class RequiresAuthenticationError extends AccountManagerError {\n public accountId: string | undefined;\n\n constructor(service: string, accountId?: string) {\n const message = accountId ? `No account found for ${service} (account: ${accountId}). Use account-add to connect one.` : `No account found for ${service}. Use account-add to connect one.`;\n super(message, 'REQUIRES_AUTHENTICATION', false);\n this.accountId = accountId;\n }\n}\n\nexport interface AccountAuthProvider {\n getAccessToken(accountId?: string): Promise<string>;\n getUserEmail(accountId?: string): Promise<string>;\n}\n\n// TODO: Remove AuthEmailProvider alias in a future major release.\nexport type AuthEmailProvider = AccountAuthProvider;\n\nexport interface UserAuthProvider {\n getUserId(req: unknown): Promise<string>; // Throws if auth invalid\n}\n\nexport interface JWTUserAuthConfig {\n secret?: string; // HS256 - MUST be at least 32 characters\n publicKey?: string | object; // RS256/ES256 - PEM string, JWK object, or JWKS URL\n jwksUrl?: string; // Alternative to publicKey\n issuer?: string | string[];\n audience?: string | string[];\n userIdClaim?: string; // Default: 'sub'\n algorithms?: string[]; // Default: auto-detect\n clockTolerance?: number; // Default: 0\n}\n\nexport interface SessionUserAuthConfig {\n sessionSecret: string; // MUST be at least 32 characters\n cookieName?: string; // Default: 'session'\n algorithm?: 'sha256' | 'sha512'; // Default: 'sha256'\n}\n\nexport interface Credentials {\n accessToken: string;\n expiresAt?: number;\n refreshToken?: string;\n scope?: string;\n tokenType?: string;\n idToken?: string;\n}\n\nexport type AuthFlowDescriptor =\n | { kind: 'credentials'; connection?: string; provider?: string; credentials: Credentials }\n | { kind: 'auth_url'; connection?: string; provider?: string; url: string; txn?: string; state?: string; codeVerifier?: string; poll?: { statusUrl: string; interval?: number }; hint?: string }\n | { kind: 'device_code'; connection?: string; provider?: string; txn?: string; device: { userCode: string; verificationUri: string; verificationUriComplete?: string; expiresIn: number; interval: number }; poll?: { statusUrl: string; interval?: number }; hint?: string }\n | { kind: 'error'; error: string; code?: number };\n\nexport class AuthRequiredError extends Error {\n public descriptor: AuthFlowDescriptor;\n\n constructor(descriptor: AuthFlowDescriptor, message?: string) {\n super(message || `Authentication required: ${descriptor.kind}`);\n this.name = 'AuthRequiredError';\n this.descriptor = descriptor;\n }\n}\n\nexport interface CachedToken {\n accessToken: string;\n refreshToken?: string;\n expiresAt?: number;\n scope?: string;\n}\n\n/**\n * Tool config signature - explicit structural type mirroring SDK registerTool config\n *\n * Uses explicit structure instead of Parameters<> extraction to avoid TypeScript inference\n * collapse to 'never' when using ToolModule[] arrays. The deep conditional types from\n * Parameters<> cannot be unified across array elements.\n *\n * Validated against SDK signature for compatibility - compile errors if SDK changes.\n *\n * NOTE: This type is duplicated in @mcp-z/server for architectural independence.\n * Keep these definitions synchronized manually when updating.\n */\nexport type ToolConfig = {\n title?: string;\n description?: string;\n inputSchema?: ZodRawShapeCompat | AnySchema;\n outputSchema?: ZodRawShapeCompat | AnySchema;\n annotations?: ToolAnnotations;\n _meta?: Record<string, unknown>;\n};\n\n// Compile-time validation that ToolConfig is compatible with SDK\ntype _ValidateToolConfigAssignable = ToolConfig extends Parameters<McpServer['registerTool']>[1] ? true : never;\ntype _ValidateToolConfigReceivable = Parameters<McpServer['registerTool']>[1] extends ToolConfig ? true : never;\n\n/**\n * Tool handler signature with generic support for middleware.\n *\n * @template TArgs - Tool arguments type (default: unknown for SDK compatibility)\n * @template TExtra - Request handler extra type (default: RequestHandlerExtra from SDK)\n *\n * Defaults provide SDK-extracted types for compatibility with MCP SDK.\n * Generic parameters enable type-safe middleware transformation.\n *\n * NOTE: This interface is duplicated in @mcp-z/server for architectural independence.\n * Keep these definitions synchronized manually when updating.\n */\nexport type ToolHandler<TArgs = unknown, TExtra = RequestHandlerExtra<ServerRequest, ServerNotification>> = (args: TArgs, extra: TExtra) => Promise<CallToolResult>;\n\n/**\n * Tool module interface with bounded generics.\n *\n * @template TConfig - Tool config type (default: SDK ToolConfig)\n * @template THandler - Handler function type (default: SDK ToolHandler)\n *\n * Use without generics for SDK-typed tools:\n * - Business tool factories: `ToolModule`\n * - Tool registration: `ToolModule[]`\n *\n * Use with generics for middleware transformation:\n * - Auth middleware: `ToolModule<ToolConfig, ToolHandler<TArgs, EnrichedExtra>>`\n *\n * The bounds ensure compatibility with SDK registration.\n *\n * NOTE: This interface is duplicated in @mcp-z/server for architectural independence.\n * Keep these definitions synchronized manually when updating.\n *\n * @see {@link ToolHandler} for handler function signature\n * @see {@link AuthMiddlewareWrapper} for middleware wrapper pattern\n */\nexport interface ToolModule<TConfig = ToolConfig, THandler = unknown> {\n name: string;\n config: TConfig;\n handler: THandler;\n}\n\n/**\n * Middleware wrapper that enriches tool modules with authentication context.\n *\n * Wraps plain tool modules to inject authentication, logging, and request metadata.\n * The wrapper pattern allows separation of business logic from cross-cutting concerns.\n *\n * @template TArgs - Tool arguments type (inferred from tool module)\n * @template TExtra - Enriched extra type with auth context and logger\n *\n * @param toolModule - Plain tool module to wrap with auth middleware\n * @returns Wrapped tool module with enriched handler signature\n *\n * @remarks\n * Auth middleware wrappers typically:\n * - Extract auth context from MCP request or OAuth provider\n * - Inject logger instance for structured logging\n * - Handle authentication errors with proper MCP error responses\n * - Preserve tool configuration and metadata\n *\n * @example\n * ```typescript\n * // Actual usage pattern from OAuth providers (LoopbackOAuthProvider, ServiceAccountProvider, DcrOAuthProvider)\n * const provider = new LoopbackOAuthProvider({ service: 'gmail', ... });\n * const authMiddleware = provider.authMiddleware();\n *\n * // Apply middleware to tools (handlers receive enriched extra with authContext)\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 enriched extra with guaranteed authContext\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 * // ... business logic with authenticated context\n * }\n * ```\n *\n * @see {@link ToolModule} for base tool interface\n * @see {@link ToolHandler} for handler function signature\n */\nexport type AuthMiddlewareWrapper<TArgs = unknown, TExtra = RequestHandlerExtra<ServerRequest, ServerNotification>> = (toolModule: ToolModule) => ToolModule<ToolConfig, ToolHandler<TArgs, TExtra>>;\n\n/**\n * Base interface for stateful OAuth adapters (LoopbackOAuthProvider pattern)\n *\n * Stateful adapters manage token storage, refresh, and multi-account state.\n * Used for local development, test setup, and CI/CD workflows.\n *\n * Key characteristics:\n * - Token storage and retrieval via tokenStore\n * - Automatic token refresh with provider\n * - Interactive OAuth flows (browser, ephemeral server)\n * - Multi-account management\n *\n * Parameter usage:\n * - accountId: Account identifier (email address for token storage)\n */\nexport interface OAuth2TokenStorageProvider {\n /**\n * Get access token for the specified account.\n * If token is expired, automatically refreshes it.\n * If token is missing, triggers OAuth flow (interactive) or throws AuthRequired (headless).\n *\n * @param accountId - Account identifier for multi-account support\n * @returns Access token string\n */\n getAccessToken(accountId?: string): Promise<string>;\n\n /**\n * Get email address for the specified account.\n * Used during account registration to verify identity with provider.\n *\n * @param accountId - Account identifier\n * @returns Email address from provider verification\n */\n getUserEmail(accountId?: string): Promise<string>;\n}\n"],"names":["AccountManagerError","AccountNotFoundError","AuthRequiredError","ConfigurationError","RequiresAuthenticationError","message","code","retryable","name","Error","accountRef","service","accountId","descriptor","kind"],"mappings":"AAAA;;CAEC;;;;;;;;;;;QAsHYA;eAAAA;;QAYAC;eAAAA;;QAkEAC;eAAAA;;QA5DAC;eAAAA;;QAMAC;eAAAA;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;AAxBN,IAAA,AAAMJ,oCAAN;;cAAMA;aAAAA,oBAICK,OAAe,EAAEC,IAAY;YAAEC,YAAAA,iEAAY;gCAJ5CP;;gBAKT,kBALSA;YAKHK;;QACN,MAAKG,IAAI,GAAG;QACZ,MAAKF,IAAI,GAAGA;QACZ,MAAKC,SAAS,GAAGA;;;WARRP;qBAA4BS;AAYlC,IAAA,AAAMR,qCAAN;;cAAMA;aAAAA,qBACCS,UAAkB;gCADnBT;QAET,OAAA,kBAFSA;YAEF,YAAsB,OAAXS,YAAW;YAAc;YAAqB;;;WAFvDT;EAA6BD;AAMnC,IAAA,AAAMG,mCAAN;;cAAMA;aAAAA,mBACCE,OAAe;gCADhBF;QAET,OAAA,kBAFSA;YAEF,wBAA+B,OAARE;YAAW;YAAuB;;;WAFvDF;EAA2BH;AAMjC,IAAA,AAAMI,4CAAN;;cAAMA;aAAAA,4BAGCO,OAAe,EAAEC,SAAkB;gCAHpCR;;QAIT,IAAMC,UAAUO,YAAY,AAAC,wBAA4CA,OAArBD,SAAQ,eAAuB,OAAVC,WAAU,wCAAsC,AAAC,wBAA+B,OAARD,SAAQ;gBACzJ,kBALSP;YAKHC;YAAS;YAA2B;;QAC1C,MAAKO,SAAS,GAAGA;;;WANRR;EAAoCJ;AAsD1C,IAAA,AAAME,kCAAN;;cAAMA;aAAAA,kBAGCW,UAA8B,EAAER,OAAgB;gCAHjDH;;gBAIT,kBAJSA;YAIHG,WAAW,AAAC,4BAA2C,OAAhBQ,WAAWC,IAAI;;QAC5D,MAAKN,IAAI,GAAG;QACZ,MAAKK,UAAU,GAAGA;;;WANTX;qBAA0BO"}
@@ -1 +1 @@
1
- {"version":3,"sources":["/Users/kevin/Dev/Projects/ai/mcp-z/oauth/oauth/src/account-utils.ts"],"sourcesContent":["/**\n * Account management utilities for OAuth token storage\n *\n * Provides account lifecycle operations (add, remove, activate) and account data\n * access (tokens, metadata). Uses named parameters consistent with key-utils.ts.\n */\n\nimport type { Keyv } from 'keyv';\nimport { type AccountKeyParams, createAccountKey, createServiceKey, type ServiceKeyParams } from './key-utils.ts';\nimport type { AccountInfo } from './types.ts';\n\n// ============================================================================\n// Account Lifecycle Operations\n// ============================================================================\n\n/**\n * Add account to linked accounts list and set as active if first account.\n *\n * @param store - Keyv storage instance\n * @param params - Account identification (service, accountId)\n *\n * @example\n * await addAccount(tokenStore, {\n * service: 'gmail',\n * accountId: 'alice@gmail.com'\n * });\n */\nexport async function addAccount(store: Keyv, params: AccountKeyParams): Promise<void> {\n const linked = await getLinkedAccounts(store, { service: params.service });\n\n if (!linked.includes(params.accountId)) {\n linked.push(params.accountId);\n const linkedKey = createServiceKey('linked', { service: params.service });\n await store.set(linkedKey, linked);\n }\n\n const active = await getActiveAccount(store, { service: params.service });\n if (!active) {\n await setActiveAccount(store, params);\n }\n}\n\n/**\n * Remove account: delete token, metadata, update linked list, and active account.\n *\n * @param store - Keyv storage instance\n * @param params - Account identification (service, accountId)\n *\n * @example\n * await removeAccount(tokenStore, {\n * service: 'gmail',\n * accountId: 'alice@gmail.com'\n * });\n */\nexport async function removeAccount(store: Keyv, params: AccountKeyParams): Promise<void> {\n const tokenKey = createAccountKey('token', params);\n await store.delete(tokenKey);\n\n const infoKey = createAccountKey('metadata', params);\n await store.delete(infoKey);\n\n const linked = await getLinkedAccounts(store, { service: params.service });\n const filtered = linked.filter((id) => id !== params.accountId);\n const linkedKey = createServiceKey('linked', { service: params.service });\n await store.set(linkedKey, filtered);\n\n // Set new active account if we're removing the currently active one\n const active = await getActiveAccount(store, { service: params.service });\n if (active === params.accountId) {\n const newActive = filtered[0];\n if (newActive) {\n await setActiveAccount(store, { service: params.service, accountId: newActive });\n } else {\n const activeKey = createServiceKey('active', { service: params.service });\n await store.delete(activeKey);\n }\n }\n}\n\n// ============================================================================\n// Service-Scoped Account Operations\n// ============================================================================\n\n/**\n * Get active account ID for a service.\n *\n * Key: {service}:active\n *\n * @param store - Keyv storage instance\n * @param params - Service identification (service)\n * @returns Active account ID or undefined if none set\n */\nexport async function getActiveAccount(store: Keyv, params: ServiceKeyParams): Promise<string | undefined> {\n const key = createServiceKey('active', params);\n return await store.get(key);\n}\n\n/**\n * Set active account ID for a service.\n * Pass null as accountId to deactivate (clear active account).\n *\n * Key: {service}:active\n *\n * @param store - Keyv storage instance\n * @param params - Account identification (service, accountId). Pass accountId: null to deactivate.\n */\nexport async function setActiveAccount(store: Keyv, params: AccountKeyParams | (ServiceKeyParams & { accountId: null })): Promise<void> {\n const key = createServiceKey('active', { service: params.service });\n if ('accountId' in params && params.accountId === null) {\n // accountId: null signals deactivation per API contract\n await store.delete(key);\n } else {\n await store.set(key, (params as AccountKeyParams).accountId);\n }\n}\n\n/**\n * Get list of linked account IDs for a service.\n *\n * Key: {service}:linked\n *\n * @param store - Keyv storage instance\n * @param params - Service identification (service)\n * @returns Array of account IDs (empty array if none)\n */\nexport async function getLinkedAccounts(store: Keyv, params: ServiceKeyParams): Promise<string[]> {\n const key = createServiceKey('linked', params);\n const accounts = await store.get(key);\n return accounts || [];\n}\n\n// ============================================================================\n// Account Data Operations\n// ============================================================================\n\n/**\n * Get account metadata (alias, lastUsed, etc).\n *\n * Key: {accountId}:{service}:metadata\n *\n * @param store - Keyv storage instance\n * @param params - Account identification (accountId, service)\n * @returns Account info or undefined if not found\n */\nexport async function getAccountInfo(store: Keyv, params: AccountKeyParams): Promise<AccountInfo | undefined> {\n const key = createAccountKey('metadata', params);\n return await store.get(key);\n}\n\n/**\n * Set account metadata (alias, lastUsed, etc).\n *\n * Key: {accountId}:{service}:metadata\n *\n * @param store - Keyv storage instance\n * @param params - Account identification (accountId, service)\n * @param info - Account metadata to store\n */\nexport async function setAccountInfo(store: Keyv, params: AccountKeyParams, info: AccountInfo): Promise<void> {\n const key = createAccountKey('metadata', params);\n await store.set(key, info);\n}\n\n/**\n * Get OAuth token for an account.\n *\n * Key: {accountId}:{service}:token\n *\n * @param store - Keyv storage instance\n * @param params - Account identification (accountId, service)\n * @returns Token or undefined if not found\n */\nexport async function getToken<T>(store: Keyv, params: AccountKeyParams): Promise<T | undefined> {\n const key = createAccountKey('token', params);\n return await store.get(key);\n}\n\n/**\n * Set OAuth token for an account.\n *\n * Key: {accountId}:{service}:token\n *\n * @param store - Keyv storage instance\n * @param params - Account identification (accountId, service)\n * @param token - OAuth token data to store\n */\nexport async function setToken<T>(store: Keyv, params: AccountKeyParams, token: T): Promise<void> {\n const key = createAccountKey('token', params);\n await store.set(key, token);\n}\n"],"names":["createAccountKey","createServiceKey","addAccount","store","params","linked","getLinkedAccounts","service","includes","accountId","push","linkedKey","set","active","getActiveAccount","setActiveAccount","removeAccount","tokenKey","delete","infoKey","filtered","filter","id","newActive","activeKey","key","get","accounts","getAccountInfo","setAccountInfo","info","getToken","setToken","token"],"mappings":"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"}
1
+ {"version":3,"sources":["/Users/kevin/Dev/Projects/mcp-z/oauth/src/account-utils.ts"],"sourcesContent":["/**\n * Account management utilities for OAuth token storage\n *\n * Provides account lifecycle operations (add, remove, activate) and account data\n * access (tokens, metadata). Uses named parameters consistent with key-utils.ts.\n */\n\nimport type { Keyv } from 'keyv';\nimport { type AccountKeyParams, createAccountKey, createServiceKey, type ServiceKeyParams } from './key-utils.ts';\nimport type { AccountInfo } from './types.ts';\n\n// ============================================================================\n// Account Lifecycle Operations\n// ============================================================================\n\n/**\n * Add account to linked accounts list and set as active if first account.\n *\n * @param store - Keyv storage instance\n * @param params - Account identification (service, accountId)\n *\n * @example\n * await addAccount(tokenStore, {\n * service: 'gmail',\n * accountId: 'alice@gmail.com'\n * });\n */\nexport async function addAccount(store: Keyv, params: AccountKeyParams): Promise<void> {\n const linked = await getLinkedAccounts(store, { service: params.service });\n\n if (!linked.includes(params.accountId)) {\n linked.push(params.accountId);\n const linkedKey = createServiceKey('linked', { service: params.service });\n await store.set(linkedKey, linked);\n }\n\n const active = await getActiveAccount(store, { service: params.service });\n if (!active) {\n await setActiveAccount(store, params);\n }\n}\n\n/**\n * Remove account: delete token, metadata, update linked list, and active account.\n *\n * @param store - Keyv storage instance\n * @param params - Account identification (service, accountId)\n *\n * @example\n * await removeAccount(tokenStore, {\n * service: 'gmail',\n * accountId: 'alice@gmail.com'\n * });\n */\nexport async function removeAccount(store: Keyv, params: AccountKeyParams): Promise<void> {\n const tokenKey = createAccountKey('token', params);\n await store.delete(tokenKey);\n\n const infoKey = createAccountKey('metadata', params);\n await store.delete(infoKey);\n\n const linked = await getLinkedAccounts(store, { service: params.service });\n const filtered = linked.filter((id) => id !== params.accountId);\n const linkedKey = createServiceKey('linked', { service: params.service });\n await store.set(linkedKey, filtered);\n\n // Set new active account if we're removing the currently active one\n const active = await getActiveAccount(store, { service: params.service });\n if (active === params.accountId) {\n const newActive = filtered[0];\n if (newActive) {\n await setActiveAccount(store, { service: params.service, accountId: newActive });\n } else {\n const activeKey = createServiceKey('active', { service: params.service });\n await store.delete(activeKey);\n }\n }\n}\n\n// ============================================================================\n// Service-Scoped Account Operations\n// ============================================================================\n\n/**\n * Get active account ID for a service.\n *\n * Key: {service}:active\n *\n * @param store - Keyv storage instance\n * @param params - Service identification (service)\n * @returns Active account ID or undefined if none set\n */\nexport async function getActiveAccount(store: Keyv, params: ServiceKeyParams): Promise<string | undefined> {\n const key = createServiceKey('active', params);\n return await store.get(key);\n}\n\n/**\n * Set active account ID for a service.\n * Pass null as accountId to deactivate (clear active account).\n *\n * Key: {service}:active\n *\n * @param store - Keyv storage instance\n * @param params - Account identification (service, accountId). Pass accountId: null to deactivate.\n */\nexport async function setActiveAccount(store: Keyv, params: AccountKeyParams | (ServiceKeyParams & { accountId: null })): Promise<void> {\n const key = createServiceKey('active', { service: params.service });\n if ('accountId' in params && params.accountId === null) {\n // accountId: null signals deactivation per API contract\n await store.delete(key);\n } else {\n await store.set(key, (params as AccountKeyParams).accountId);\n }\n}\n\n/**\n * Get list of linked account IDs for a service.\n *\n * Key: {service}:linked\n *\n * @param store - Keyv storage instance\n * @param params - Service identification (service)\n * @returns Array of account IDs (empty array if none)\n */\nexport async function getLinkedAccounts(store: Keyv, params: ServiceKeyParams): Promise<string[]> {\n const key = createServiceKey('linked', params);\n const accounts = await store.get(key);\n return accounts || [];\n}\n\n// ============================================================================\n// Account Data Operations\n// ============================================================================\n\n/**\n * Get account metadata (alias, lastUsed, etc).\n *\n * Key: {accountId}:{service}:metadata\n *\n * @param store - Keyv storage instance\n * @param params - Account identification (accountId, service)\n * @returns Account info or undefined if not found\n */\nexport async function getAccountInfo(store: Keyv, params: AccountKeyParams): Promise<AccountInfo | undefined> {\n const key = createAccountKey('metadata', params);\n return await store.get(key);\n}\n\n/**\n * Set account metadata (alias, lastUsed, etc).\n *\n * Key: {accountId}:{service}:metadata\n *\n * @param store - Keyv storage instance\n * @param params - Account identification (accountId, service)\n * @param info - Account metadata to store\n */\nexport async function setAccountInfo(store: Keyv, params: AccountKeyParams, info: AccountInfo): Promise<void> {\n const key = createAccountKey('metadata', params);\n await store.set(key, info);\n}\n\n/**\n * Get OAuth token for an account.\n *\n * Key: {accountId}:{service}:token\n *\n * @param store - Keyv storage instance\n * @param params - Account identification (accountId, service)\n * @returns Token or undefined if not found\n */\nexport async function getToken<T>(store: Keyv, params: AccountKeyParams): Promise<T | undefined> {\n const key = createAccountKey('token', params);\n return await store.get(key);\n}\n\n/**\n * Set OAuth token for an account.\n *\n * Key: {accountId}:{service}:token\n *\n * @param store - Keyv storage instance\n * @param params - Account identification (accountId, service)\n * @param token - OAuth token data to store\n */\nexport async function setToken<T>(store: Keyv, params: AccountKeyParams, token: T): Promise<void> {\n const key = createAccountKey('token', params);\n await store.set(key, token);\n}\n"],"names":["createAccountKey","createServiceKey","addAccount","store","params","linked","getLinkedAccounts","service","includes","accountId","push","linkedKey","set","active","getActiveAccount","setActiveAccount","removeAccount","tokenKey","delete","infoKey","filtered","filter","id","newActive","activeKey","key","get","accounts","getAccountInfo","setAccountInfo","info","getToken","setToken","token"],"mappings":"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"}
@@ -15,5 +15,5 @@ export { sanitizeForLogging, sanitizeForLoggingFormatter } from './sanitizer.js'
15
15
  export * as schemas from './schemas/index.js';
16
16
  export { SessionUserAuth } from './session-auth.js';
17
17
  export { getErrorTemplate, getSuccessTemplate } from './templates.js';
18
- export type { AccountInfo, AuthEmailProvider, AuthFlowDescriptor, AuthMiddlewareWrapper, CachedToken, Credentials, JWTUserAuthConfig, Logger, McpPrompt, McpTool, OAuth2TokenStorageProvider, SessionUserAuthConfig, ToolConfig, ToolHandler, ToolModule, UserAuthProvider, } from './types.js';
18
+ export type { AccountAuthProvider, AccountInfo, AuthEmailProvider, AuthFlowDescriptor, AuthMiddlewareWrapper, CachedToken, Credentials, JWTUserAuthConfig, Logger, McpPrompt, McpTool, OAuth2TokenStorageProvider, SessionUserAuthConfig, ToolConfig, ToolHandler, ToolModule, UserAuthProvider, } from './types.js';
19
19
  export { AccountManagerError, AccountNotFoundError, AuthRequiredError, ConfigurationError, RequiresAuthenticationError } from './types.js';
@@ -1 +1 @@
1
- {"version":3,"sources":["/Users/kevin/Dev/Projects/ai/mcp-z/oauth/oauth/src/index.ts"],"sourcesContent":["/**\n * @mcp-z/oauth - Multi-account OAuth orchestration and secure token storage for MCP servers\n *\n * Provides account management functions, account tools generation, and secure logging utilities.\n * Designed to work with any storage backend (file, Redis, DuckDB) via Keyv interface.\n */\n\n// Core account management - Public API\n// Internal functions - For provider implementations and testing\nexport { addAccount, getActiveAccount, getToken, removeAccount, setAccountInfo, setActiveAccount, setToken } from './account-utils.ts';\n// Auth classes - For multi-tenant testing\nexport { JWTUserAuth } from './jwt-auth.ts';\nexport { type AccountKeyParams, type AccountKeyType, createAccountKey, createServiceKey, listAccountIds, type ServiceKeyParams, type ServiceKeyType } from './key-utils.ts';\n// Account server and factory functions - Public API\nexport { type AccountLoopbackConfig, AccountServer, type AccountStatelessConfig, createLoopback, createStateless } from './lib/account-server/index.ts';\n// DCR types - Public API\nexport type { DcrClientInformation, DcrClientMetadata, DcrConfig, DcrErrorResponse, ProviderTokens } from './lib/dcr-types.ts';\n// RFC Metadata Types - Public API\nexport type { RFC8414Metadata, RFC9728Metadata } from './lib/rfc-metadata-types.ts';\nexport { generatePKCE, type PKCEPair } from './pkce.ts';\n// Logging utilities - Public API\nexport { sanitizeForLogging, sanitizeForLoggingFormatter } from './sanitizer.ts';\n// Schemas\nexport * as schemas from './schemas/index.ts';\nexport { SessionUserAuth } from './session-auth.ts';\nexport { getErrorTemplate, getSuccessTemplate } from './templates.ts';\n\n// Public types - core interfaces that consumers use\nexport type {\n // Account management types\n AccountInfo,\n // Provider interfaces\n AuthEmailProvider,\n AuthFlowDescriptor,\n AuthMiddlewareWrapper,\n CachedToken,\n Credentials,\n // Auth config types\n JWTUserAuthConfig,\n // Utility types\n Logger,\n McpPrompt,\n McpTool,\n OAuth2TokenStorageProvider,\n SessionUserAuthConfig,\n ToolConfig,\n ToolHandler,\n ToolModule,\n UserAuthProvider,\n} from './types.ts';\n\n// Public error classes\nexport { AccountManagerError, AccountNotFoundError, AuthRequiredError, ConfigurationError, RequiresAuthenticationError } from './types.ts';\n"],"names":["addAccount","getActiveAccount","getToken","removeAccount","setAccountInfo","setActiveAccount","setToken","JWTUserAuth","createAccountKey","createServiceKey","listAccountIds","AccountServer","createLoopback","createStateless","generatePKCE","sanitizeForLogging","sanitizeForLoggingFormatter","schemas","SessionUserAuth","getErrorTemplate","getSuccessTemplate","AccountManagerError","AccountNotFoundError","AuthRequiredError","ConfigurationError","RequiresAuthenticationError"],"mappings":"AAAA;;;;;CAKC,GAED,uCAAuC;AACvC,gEAAgE;AAChE,SAASA,UAAU,EAAEC,gBAAgB,EAAEC,QAAQ,EAAEC,aAAa,EAAEC,cAAc,EAAEC,gBAAgB,EAAEC,QAAQ,QAAQ,qBAAqB;AACvI,0CAA0C;AAC1C,SAASC,WAAW,QAAQ,gBAAgB;AAC5C,SAAqDC,gBAAgB,EAAEC,gBAAgB,EAAEC,cAAc,QAAoD,iBAAiB;AAC5K,oDAAoD;AACpD,SAAqCC,aAAa,EAA+BC,cAAc,EAAEC,eAAe,QAAQ,gCAAgC;AAKxJ,SAASC,YAAY,QAAuB,YAAY;AACxD,iCAAiC;AACjC,SAASC,kBAAkB,EAAEC,2BAA2B,QAAQ,iBAAiB;AACjF,UAAU;AACV,0BAAyB,qBAAqB;AAA9C,SAAO,YAAKC,OAAO,GAA2B;AAC9C,SAASC,eAAe,QAAQ,oBAAoB;AACpD,SAASC,gBAAgB,EAAEC,kBAAkB,QAAQ,iBAAiB;AA0BtE,uBAAuB;AACvB,SAASC,mBAAmB,EAAEC,oBAAoB,EAAEC,iBAAiB,EAAEC,kBAAkB,EAAEC,2BAA2B,QAAQ,aAAa"}
1
+ {"version":3,"sources":["/Users/kevin/Dev/Projects/mcp-z/oauth/src/index.ts"],"sourcesContent":["/**\n * @mcp-z/oauth - Multi-account OAuth orchestration and secure token storage for MCP servers\n *\n * Provides account management functions, account tools generation, and secure logging utilities.\n * Designed to work with any storage backend (file, Redis, DuckDB) via Keyv interface.\n */\n\n// Core account management - Public API\n// Internal functions - For provider implementations and testing\nexport { addAccount, getActiveAccount, getToken, removeAccount, setAccountInfo, setActiveAccount, setToken } from './account-utils.ts';\n// Auth classes - For multi-tenant testing\nexport { JWTUserAuth } from './jwt-auth.ts';\nexport { type AccountKeyParams, type AccountKeyType, createAccountKey, createServiceKey, listAccountIds, type ServiceKeyParams, type ServiceKeyType } from './key-utils.ts';\n// Account server and factory functions - Public API\nexport { type AccountLoopbackConfig, AccountServer, type AccountStatelessConfig, createLoopback, createStateless } from './lib/account-server/index.ts';\n// DCR types - Public API\nexport type { DcrClientInformation, DcrClientMetadata, DcrConfig, DcrErrorResponse, ProviderTokens } from './lib/dcr-types.ts';\n// RFC Metadata Types - Public API\nexport type { RFC8414Metadata, RFC9728Metadata } from './lib/rfc-metadata-types.ts';\nexport { generatePKCE, type PKCEPair } from './pkce.ts';\n// Logging utilities - Public API\nexport { sanitizeForLogging, sanitizeForLoggingFormatter } from './sanitizer.ts';\n// Schemas\nexport * as schemas from './schemas/index.ts';\nexport { SessionUserAuth } from './session-auth.ts';\nexport { getErrorTemplate, getSuccessTemplate } from './templates.ts';\n\n// Public types - core interfaces that consumers use\nexport type {\n // Provider interfaces\n AccountAuthProvider,\n // Account management types\n AccountInfo,\n AuthEmailProvider,\n AuthFlowDescriptor,\n AuthMiddlewareWrapper,\n CachedToken,\n Credentials,\n // Auth config types\n JWTUserAuthConfig,\n // Utility types\n Logger,\n McpPrompt,\n McpTool,\n OAuth2TokenStorageProvider,\n SessionUserAuthConfig,\n ToolConfig,\n ToolHandler,\n ToolModule,\n UserAuthProvider,\n} from './types.ts';\n\n// Public error classes\nexport { AccountManagerError, AccountNotFoundError, AuthRequiredError, ConfigurationError, RequiresAuthenticationError } from './types.ts';\n"],"names":["addAccount","getActiveAccount","getToken","removeAccount","setAccountInfo","setActiveAccount","setToken","JWTUserAuth","createAccountKey","createServiceKey","listAccountIds","AccountServer","createLoopback","createStateless","generatePKCE","sanitizeForLogging","sanitizeForLoggingFormatter","schemas","SessionUserAuth","getErrorTemplate","getSuccessTemplate","AccountManagerError","AccountNotFoundError","AuthRequiredError","ConfigurationError","RequiresAuthenticationError"],"mappings":"AAAA;;;;;CAKC,GAED,uCAAuC;AACvC,gEAAgE;AAChE,SAASA,UAAU,EAAEC,gBAAgB,EAAEC,QAAQ,EAAEC,aAAa,EAAEC,cAAc,EAAEC,gBAAgB,EAAEC,QAAQ,QAAQ,qBAAqB;AACvI,0CAA0C;AAC1C,SAASC,WAAW,QAAQ,gBAAgB;AAC5C,SAAqDC,gBAAgB,EAAEC,gBAAgB,EAAEC,cAAc,QAAoD,iBAAiB;AAC5K,oDAAoD;AACpD,SAAqCC,aAAa,EAA+BC,cAAc,EAAEC,eAAe,QAAQ,gCAAgC;AAKxJ,SAASC,YAAY,QAAuB,YAAY;AACxD,iCAAiC;AACjC,SAASC,kBAAkB,EAAEC,2BAA2B,QAAQ,iBAAiB;AACjF,UAAU;AACV,0BAAyB,qBAAqB;AAA9C,SAAO,YAAKC,OAAO,GAA2B;AAC9C,SAASC,eAAe,QAAQ,oBAAoB;AACpD,SAASC,gBAAgB,EAAEC,kBAAkB,QAAQ,iBAAiB;AA2BtE,uBAAuB;AACvB,SAASC,mBAAmB,EAAEC,oBAAoB,EAAEC,iBAAiB,EAAEC,kBAAkB,EAAEC,2BAA2B,QAAQ,aAAa"}
@@ -1 +1 @@
1
- {"version":3,"sources":["/Users/kevin/Dev/Projects/ai/mcp-z/oauth/oauth/src/jwt-auth.ts"],"sourcesContent":["/**\n * JWT-based user authentication for multi-tenant deployments\n *\n * Extracts user ID from JWT tokens with signature and claims verification.\n * Supports HS256, RS256, ES256 algorithms via JOSE library.\n */\n\nimport { createRemoteJWKSet, importSPKI, type JWK, type JWTPayload, type JWTVerifyOptions, type JWTVerifyResult, jwtVerify } from 'jose';\nimport type { JWTUserAuthConfig, UserAuthProvider } from './types.ts';\n\n/**\n * HTTP request interface (subset needed for JWT auth)\n */\ninterface HttpRequest {\n headers?: {\n authorization?: string;\n };\n}\n\n/**\n * JWT-based user authentication provider\n *\n * Verifies JWT tokens and extracts user IDs from claims.\n * Use for multi-tenant deployments where users authenticate via JWT.\n *\n * @example\n * ```typescript\n * // HS256 with shared secret\n * const userAuth = new JWTUserAuth({\n * secret: process.env.JWT_SECRET!,\n * issuer: 'https://auth.example.com',\n * audience: 'api.example.com',\n * });\n *\n * // RS256 with public key\n * const userAuth = new JWTUserAuth({\n * publicKey: process.env.JWT_PUBLIC_KEY!,\n * issuer: 'https://auth.example.com',\n * });\n *\n * // RS256 with JWKS URL (dynamic key rotation)\n * const userAuth = new JWTUserAuth({\n * jwksUrl: 'https://auth.example.com/.well-known/jwks.json',\n * issuer: 'https://auth.example.com',\n * audience: 'api.example.com',\n * });\n * ```\n */\nexport class JWTUserAuth implements UserAuthProvider {\n private readonly config: {\n secret?: string;\n publicKey?: string | JWK;\n jwksUrl?: string;\n issuer?: string | string[];\n audience?: string | string[];\n userIdClaim: string;\n algorithms: string[];\n clockTolerance: number;\n };\n private readonly remoteJWKSet?: ReturnType<typeof createRemoteJWKSet>;\n\n constructor(config: JWTUserAuthConfig) {\n // Validate configuration\n if (!config.secret && !config.publicKey && !config.jwksUrl) {\n throw new Error('JWTUserAuth: Must provide one of: secret (HS256), publicKey (RS256/ES256), or jwksUrl');\n }\n\n if (config.secret && config.secret.length < 32) {\n throw new Error('JWTUserAuth: secret must be at least 32 characters for HS256');\n }\n\n if ((config.secret ? 1 : 0) + (config.publicKey ? 1 : 0) + (config.jwksUrl ? 1 : 0) > 1) {\n throw new Error('JWTUserAuth: Provide only one of: secret, publicKey, or jwksUrl');\n }\n\n // Store configuration with defaults\n this.config = {\n ...(config.secret !== undefined && { secret: config.secret }),\n ...(config.publicKey !== undefined && { publicKey: config.publicKey }),\n ...(config.jwksUrl !== undefined && { jwksUrl: config.jwksUrl }),\n ...(config.issuer !== undefined && { issuer: config.issuer }),\n ...(config.audience !== undefined && { audience: config.audience }),\n userIdClaim: config.userIdClaim ?? 'sub',\n algorithms: config.algorithms ?? [],\n clockTolerance: config.clockTolerance ?? 0,\n };\n\n // Create remote JWK set if using JWKS URL\n if (config.jwksUrl) {\n this.remoteJWKSet = createRemoteJWKSet(new URL(config.jwksUrl));\n }\n }\n\n /**\n * Extract and verify user ID from JWT token\n *\n * @param req - HTTP request object with Authorization header\n * @returns User ID from verified JWT claims\n * @throws Error if token missing, invalid, expired, or claims invalid\n */\n async getUserId(req: unknown): Promise<string> {\n const httpReq = req as HttpRequest;\n\n // Extract Authorization header\n const authHeader = httpReq.headers?.authorization;\n if (!authHeader) {\n throw new Error('JWTUserAuth: No Authorization header found');\n }\n\n // Parse Bearer token\n const match = /^Bearer\\s+(.+)$/i.exec(authHeader.trim());\n if (!match) {\n throw new Error('JWTUserAuth: Invalid Authorization header format (expected \"Bearer <token>\")');\n }\n\n const token = match[1];\n if (!token) {\n throw new Error('JWTUserAuth: Empty JWT token');\n }\n\n // Verify JWT and extract payload\n const payload = await this.verifyToken(token);\n\n // Extract user ID from configured claim\n const userId = payload[this.config.userIdClaim];\n if (!userId || typeof userId !== 'string') {\n throw new Error(`JWTUserAuth: JWT missing or invalid '${this.config.userIdClaim}' claim`);\n }\n\n return userId;\n }\n\n /**\n * Verify JWT signature and claims\n */\n private async verifyToken(token: string): Promise<JWTPayload> {\n try {\n // Build verification options\n const options: JWTVerifyOptions = {\n ...(this.config.issuer && { issuer: this.config.issuer }),\n ...(this.config.audience && { audience: this.config.audience }),\n ...(this.config.clockTolerance && { clockTolerance: this.config.clockTolerance }),\n };\n\n // Verify with appropriate key type\n let result: JWTVerifyResult;\n\n if (this.config.secret) {\n // HS256 verification with shared secret\n const secret = new TextEncoder().encode(this.config.secret);\n result = await jwtVerify(token, secret, {\n ...options,\n algorithms: this.config.algorithms.length > 0 ? this.config.algorithms : ['HS256'],\n });\n } else if (this.remoteJWKSet) {\n // RS256/ES256 verification with remote JWKS\n result = await jwtVerify(token, this.remoteJWKSet, {\n ...options,\n algorithms: this.config.algorithms.length > 0 ? this.config.algorithms : ['RS256', 'ES256'],\n });\n } else if (this.config.publicKey) {\n // RS256/ES256 verification with provided public key\n // If string (PEM), import it first; if JWK, use directly\n const key = typeof this.config.publicKey === 'string' ? await importSPKI(this.config.publicKey, 'RS256') : this.config.publicKey;\n\n result = await jwtVerify(token, key, {\n ...options,\n algorithms: this.config.algorithms.length > 0 ? this.config.algorithms : ['RS256', 'ES256'],\n });\n } else {\n throw new Error('JWTUserAuth: No verification key configured');\n }\n\n return result.payload;\n } catch (error) {\n if (error instanceof Error) {\n throw new Error(`JWTUserAuth: JWT verification failed: ${error.message}`);\n }\n throw new Error('JWTUserAuth: JWT verification failed');\n }\n }\n}\n"],"names":["createRemoteJWKSet","importSPKI","jwtVerify","JWTUserAuth","getUserId","req","httpReq","authHeader","headers","authorization","Error","match","exec","trim","token","payload","verifyToken","userId","config","userIdClaim","options","issuer","audience","clockTolerance","result","secret","TextEncoder","encode","algorithms","length","remoteJWKSet","publicKey","key","error","message","jwksUrl","undefined","URL"],"mappings":"AAAA;;;;;CAKC,GAED,SAASA,kBAAkB,EAAEC,UAAU,EAA0EC,SAAS,QAAQ,OAAO;AAYzI;;;;;;;;;;;;;;;;;;;;;;;;;;;;CA4BC,GACD,OAAO,MAAMC;IA6CX;;;;;;GAMC,GACD,MAAMC,UAAUC,GAAY,EAAmB;YAI1BC;QAHnB,MAAMA,UAAUD;QAEhB,+BAA+B;QAC/B,MAAME,cAAaD,mBAAAA,QAAQE,OAAO,cAAfF,uCAAAA,iBAAiBG,aAAa;QACjD,IAAI,CAACF,YAAY;YACf,MAAM,IAAIG,MAAM;QAClB;QAEA,qBAAqB;QACrB,MAAMC,QAAQ,mBAAmBC,IAAI,CAACL,WAAWM,IAAI;QACrD,IAAI,CAACF,OAAO;YACV,MAAM,IAAID,MAAM;QAClB;QAEA,MAAMI,QAAQH,KAAK,CAAC,EAAE;QACtB,IAAI,CAACG,OAAO;YACV,MAAM,IAAIJ,MAAM;QAClB;QAEA,iCAAiC;QACjC,MAAMK,UAAU,MAAM,IAAI,CAACC,WAAW,CAACF;QAEvC,wCAAwC;QACxC,MAAMG,SAASF,OAAO,CAAC,IAAI,CAACG,MAAM,CAACC,WAAW,CAAC;QAC/C,IAAI,CAACF,UAAU,OAAOA,WAAW,UAAU;YACzC,MAAM,IAAIP,MAAM,CAAC,qCAAqC,EAAE,IAAI,CAACQ,MAAM,CAACC,WAAW,CAAC,OAAO,CAAC;QAC1F;QAEA,OAAOF;IACT;IAEA;;GAEC,GACD,MAAcD,YAAYF,KAAa,EAAuB;QAC5D,IAAI;YACF,6BAA6B;YAC7B,MAAMM,UAA4B;gBAChC,GAAI,IAAI,CAACF,MAAM,CAACG,MAAM,IAAI;oBAAEA,QAAQ,IAAI,CAACH,MAAM,CAACG,MAAM;gBAAC,CAAC;gBACxD,GAAI,IAAI,CAACH,MAAM,CAACI,QAAQ,IAAI;oBAAEA,UAAU,IAAI,CAACJ,MAAM,CAACI,QAAQ;gBAAC,CAAC;gBAC9D,GAAI,IAAI,CAACJ,MAAM,CAACK,cAAc,IAAI;oBAAEA,gBAAgB,IAAI,CAACL,MAAM,CAACK,cAAc;gBAAC,CAAC;YAClF;YAEA,mCAAmC;YACnC,IAAIC;YAEJ,IAAI,IAAI,CAACN,MAAM,CAACO,MAAM,EAAE;gBACtB,wCAAwC;gBACxC,MAAMA,SAAS,IAAIC,cAAcC,MAAM,CAAC,IAAI,CAACT,MAAM,CAACO,MAAM;gBAC1DD,SAAS,MAAMtB,UAAUY,OAAOW,QAAQ;oBACtC,GAAGL,OAAO;oBACVQ,YAAY,IAAI,CAACV,MAAM,CAACU,UAAU,CAACC,MAAM,GAAG,IAAI,IAAI,CAACX,MAAM,CAACU,UAAU,GAAG;wBAAC;qBAAQ;gBACpF;YACF,OAAO,IAAI,IAAI,CAACE,YAAY,EAAE;gBAC5B,4CAA4C;gBAC5CN,SAAS,MAAMtB,UAAUY,OAAO,IAAI,CAACgB,YAAY,EAAE;oBACjD,GAAGV,OAAO;oBACVQ,YAAY,IAAI,CAACV,MAAM,CAACU,UAAU,CAACC,MAAM,GAAG,IAAI,IAAI,CAACX,MAAM,CAACU,UAAU,GAAG;wBAAC;wBAAS;qBAAQ;gBAC7F;YACF,OAAO,IAAI,IAAI,CAACV,MAAM,CAACa,SAAS,EAAE;gBAChC,oDAAoD;gBACpD,yDAAyD;gBACzD,MAAMC,MAAM,OAAO,IAAI,CAACd,MAAM,CAACa,SAAS,KAAK,WAAW,MAAM9B,WAAW,IAAI,CAACiB,MAAM,CAACa,SAAS,EAAE,WAAW,IAAI,CAACb,MAAM,CAACa,SAAS;gBAEhIP,SAAS,MAAMtB,UAAUY,OAAOkB,KAAK;oBACnC,GAAGZ,OAAO;oBACVQ,YAAY,IAAI,CAACV,MAAM,CAACU,UAAU,CAACC,MAAM,GAAG,IAAI,IAAI,CAACX,MAAM,CAACU,UAAU,GAAG;wBAAC;wBAAS;qBAAQ;gBAC7F;YACF,OAAO;gBACL,MAAM,IAAIlB,MAAM;YAClB;YAEA,OAAOc,OAAOT,OAAO;QACvB,EAAE,OAAOkB,OAAO;YACd,IAAIA,iBAAiBvB,OAAO;gBAC1B,MAAM,IAAIA,MAAM,CAAC,sCAAsC,EAAEuB,MAAMC,OAAO,EAAE;YAC1E;YACA,MAAM,IAAIxB,MAAM;QAClB;IACF;IAvHA,YAAYQ,MAAyB,CAAE;YAqBtBA,qBACDA,oBACIA;QAtBlB,yBAAyB;QACzB,IAAI,CAACA,OAAOO,MAAM,IAAI,CAACP,OAAOa,SAAS,IAAI,CAACb,OAAOiB,OAAO,EAAE;YAC1D,MAAM,IAAIzB,MAAM;QAClB;QAEA,IAAIQ,OAAOO,MAAM,IAAIP,OAAOO,MAAM,CAACI,MAAM,GAAG,IAAI;YAC9C,MAAM,IAAInB,MAAM;QAClB;QAEA,IAAI,AAACQ,CAAAA,OAAOO,MAAM,GAAG,IAAI,CAAA,IAAMP,CAAAA,OAAOa,SAAS,GAAG,IAAI,CAAA,IAAMb,CAAAA,OAAOiB,OAAO,GAAG,IAAI,CAAA,IAAK,GAAG;YACvF,MAAM,IAAIzB,MAAM;QAClB;QAEA,oCAAoC;QACpC,IAAI,CAACQ,MAAM,GAAG;YACZ,GAAIA,OAAOO,MAAM,KAAKW,aAAa;gBAAEX,QAAQP,OAAOO,MAAM;YAAC,CAAC;YAC5D,GAAIP,OAAOa,SAAS,KAAKK,aAAa;gBAAEL,WAAWb,OAAOa,SAAS;YAAC,CAAC;YACrE,GAAIb,OAAOiB,OAAO,KAAKC,aAAa;gBAAED,SAASjB,OAAOiB,OAAO;YAAC,CAAC;YAC/D,GAAIjB,OAAOG,MAAM,KAAKe,aAAa;gBAAEf,QAAQH,OAAOG,MAAM;YAAC,CAAC;YAC5D,GAAIH,OAAOI,QAAQ,KAAKc,aAAa;gBAAEd,UAAUJ,OAAOI,QAAQ;YAAC,CAAC;YAClEH,WAAW,GAAED,sBAAAA,OAAOC,WAAW,cAAlBD,iCAAAA,sBAAsB;YACnCU,UAAU,GAAEV,qBAAAA,OAAOU,UAAU,cAAjBV,gCAAAA,qBAAqB,EAAE;YACnCK,cAAc,GAAEL,yBAAAA,OAAOK,cAAc,cAArBL,oCAAAA,yBAAyB;QAC3C;QAEA,0CAA0C;QAC1C,IAAIA,OAAOiB,OAAO,EAAE;YAClB,IAAI,CAACL,YAAY,GAAG9B,mBAAmB,IAAIqC,IAAInB,OAAOiB,OAAO;QAC/D;IACF;AA0FF"}
1
+ {"version":3,"sources":["/Users/kevin/Dev/Projects/mcp-z/oauth/src/jwt-auth.ts"],"sourcesContent":["/**\n * JWT-based user authentication for multi-tenant deployments\n *\n * Extracts user ID from JWT tokens with signature and claims verification.\n * Supports HS256, RS256, ES256 algorithms via JOSE library.\n */\n\nimport { createRemoteJWKSet, importSPKI, type JWK, type JWTPayload, type JWTVerifyOptions, type JWTVerifyResult, jwtVerify } from 'jose';\nimport type { JWTUserAuthConfig, UserAuthProvider } from './types.ts';\n\n/**\n * HTTP request interface (subset needed for JWT auth)\n */\ninterface HttpRequest {\n headers?: {\n authorization?: string;\n };\n}\n\n/**\n * JWT-based user authentication provider\n *\n * Verifies JWT tokens and extracts user IDs from claims.\n * Use for multi-tenant deployments where users authenticate via JWT.\n *\n * @example\n * ```typescript\n * // HS256 with shared secret\n * const userAuth = new JWTUserAuth({\n * secret: process.env.JWT_SECRET!,\n * issuer: 'https://auth.example.com',\n * audience: 'api.example.com',\n * });\n *\n * // RS256 with public key\n * const userAuth = new JWTUserAuth({\n * publicKey: process.env.JWT_PUBLIC_KEY!,\n * issuer: 'https://auth.example.com',\n * });\n *\n * // RS256 with JWKS URL (dynamic key rotation)\n * const userAuth = new JWTUserAuth({\n * jwksUrl: 'https://auth.example.com/.well-known/jwks.json',\n * issuer: 'https://auth.example.com',\n * audience: 'api.example.com',\n * });\n * ```\n */\nexport class JWTUserAuth implements UserAuthProvider {\n private readonly config: {\n secret?: string;\n publicKey?: string | JWK;\n jwksUrl?: string;\n issuer?: string | string[];\n audience?: string | string[];\n userIdClaim: string;\n algorithms: string[];\n clockTolerance: number;\n };\n private readonly remoteJWKSet?: ReturnType<typeof createRemoteJWKSet>;\n\n constructor(config: JWTUserAuthConfig) {\n // Validate configuration\n if (!config.secret && !config.publicKey && !config.jwksUrl) {\n throw new Error('JWTUserAuth: Must provide one of: secret (HS256), publicKey (RS256/ES256), or jwksUrl');\n }\n\n if (config.secret && config.secret.length < 32) {\n throw new Error('JWTUserAuth: secret must be at least 32 characters for HS256');\n }\n\n if ((config.secret ? 1 : 0) + (config.publicKey ? 1 : 0) + (config.jwksUrl ? 1 : 0) > 1) {\n throw new Error('JWTUserAuth: Provide only one of: secret, publicKey, or jwksUrl');\n }\n\n // Store configuration with defaults\n this.config = {\n ...(config.secret !== undefined && { secret: config.secret }),\n ...(config.publicKey !== undefined && { publicKey: config.publicKey }),\n ...(config.jwksUrl !== undefined && { jwksUrl: config.jwksUrl }),\n ...(config.issuer !== undefined && { issuer: config.issuer }),\n ...(config.audience !== undefined && { audience: config.audience }),\n userIdClaim: config.userIdClaim ?? 'sub',\n algorithms: config.algorithms ?? [],\n clockTolerance: config.clockTolerance ?? 0,\n };\n\n // Create remote JWK set if using JWKS URL\n if (config.jwksUrl) {\n this.remoteJWKSet = createRemoteJWKSet(new URL(config.jwksUrl));\n }\n }\n\n /**\n * Extract and verify user ID from JWT token\n *\n * @param req - HTTP request object with Authorization header\n * @returns User ID from verified JWT claims\n * @throws Error if token missing, invalid, expired, or claims invalid\n */\n async getUserId(req: unknown): Promise<string> {\n const httpReq = req as HttpRequest;\n\n // Extract Authorization header\n const authHeader = httpReq.headers?.authorization;\n if (!authHeader) {\n throw new Error('JWTUserAuth: No Authorization header found');\n }\n\n // Parse Bearer token\n const match = /^Bearer\\s+(.+)$/i.exec(authHeader.trim());\n if (!match) {\n throw new Error('JWTUserAuth: Invalid Authorization header format (expected \"Bearer <token>\")');\n }\n\n const token = match[1];\n if (!token) {\n throw new Error('JWTUserAuth: Empty JWT token');\n }\n\n // Verify JWT and extract payload\n const payload = await this.verifyToken(token);\n\n // Extract user ID from configured claim\n const userId = payload[this.config.userIdClaim];\n if (!userId || typeof userId !== 'string') {\n throw new Error(`JWTUserAuth: JWT missing or invalid '${this.config.userIdClaim}' claim`);\n }\n\n return userId;\n }\n\n /**\n * Verify JWT signature and claims\n */\n private async verifyToken(token: string): Promise<JWTPayload> {\n try {\n // Build verification options\n const options: JWTVerifyOptions = {\n ...(this.config.issuer && { issuer: this.config.issuer }),\n ...(this.config.audience && { audience: this.config.audience }),\n ...(this.config.clockTolerance && { clockTolerance: this.config.clockTolerance }),\n };\n\n // Verify with appropriate key type\n let result: JWTVerifyResult;\n\n if (this.config.secret) {\n // HS256 verification with shared secret\n const secret = new TextEncoder().encode(this.config.secret);\n result = await jwtVerify(token, secret, {\n ...options,\n algorithms: this.config.algorithms.length > 0 ? this.config.algorithms : ['HS256'],\n });\n } else if (this.remoteJWKSet) {\n // RS256/ES256 verification with remote JWKS\n result = await jwtVerify(token, this.remoteJWKSet, {\n ...options,\n algorithms: this.config.algorithms.length > 0 ? this.config.algorithms : ['RS256', 'ES256'],\n });\n } else if (this.config.publicKey) {\n // RS256/ES256 verification with provided public key\n // If string (PEM), import it first; if JWK, use directly\n const key = typeof this.config.publicKey === 'string' ? await importSPKI(this.config.publicKey, 'RS256') : this.config.publicKey;\n\n result = await jwtVerify(token, key, {\n ...options,\n algorithms: this.config.algorithms.length > 0 ? this.config.algorithms : ['RS256', 'ES256'],\n });\n } else {\n throw new Error('JWTUserAuth: No verification key configured');\n }\n\n return result.payload;\n } catch (error) {\n if (error instanceof Error) {\n throw new Error(`JWTUserAuth: JWT verification failed: ${error.message}`);\n }\n throw new Error('JWTUserAuth: JWT verification failed');\n }\n }\n}\n"],"names":["createRemoteJWKSet","importSPKI","jwtVerify","JWTUserAuth","getUserId","req","httpReq","authHeader","headers","authorization","Error","match","exec","trim","token","payload","verifyToken","userId","config","userIdClaim","options","issuer","audience","clockTolerance","result","secret","TextEncoder","encode","algorithms","length","remoteJWKSet","publicKey","key","error","message","jwksUrl","undefined","URL"],"mappings":"AAAA;;;;;CAKC,GAED,SAASA,kBAAkB,EAAEC,UAAU,EAA0EC,SAAS,QAAQ,OAAO;AAYzI;;;;;;;;;;;;;;;;;;;;;;;;;;;;CA4BC,GACD,OAAO,MAAMC;IA6CX;;;;;;GAMC,GACD,MAAMC,UAAUC,GAAY,EAAmB;YAI1BC;QAHnB,MAAMA,UAAUD;QAEhB,+BAA+B;QAC/B,MAAME,cAAaD,mBAAAA,QAAQE,OAAO,cAAfF,uCAAAA,iBAAiBG,aAAa;QACjD,IAAI,CAACF,YAAY;YACf,MAAM,IAAIG,MAAM;QAClB;QAEA,qBAAqB;QACrB,MAAMC,QAAQ,mBAAmBC,IAAI,CAACL,WAAWM,IAAI;QACrD,IAAI,CAACF,OAAO;YACV,MAAM,IAAID,MAAM;QAClB;QAEA,MAAMI,QAAQH,KAAK,CAAC,EAAE;QACtB,IAAI,CAACG,OAAO;YACV,MAAM,IAAIJ,MAAM;QAClB;QAEA,iCAAiC;QACjC,MAAMK,UAAU,MAAM,IAAI,CAACC,WAAW,CAACF;QAEvC,wCAAwC;QACxC,MAAMG,SAASF,OAAO,CAAC,IAAI,CAACG,MAAM,CAACC,WAAW,CAAC;QAC/C,IAAI,CAACF,UAAU,OAAOA,WAAW,UAAU;YACzC,MAAM,IAAIP,MAAM,CAAC,qCAAqC,EAAE,IAAI,CAACQ,MAAM,CAACC,WAAW,CAAC,OAAO,CAAC;QAC1F;QAEA,OAAOF;IACT;IAEA;;GAEC,GACD,MAAcD,YAAYF,KAAa,EAAuB;QAC5D,IAAI;YACF,6BAA6B;YAC7B,MAAMM,UAA4B;gBAChC,GAAI,IAAI,CAACF,MAAM,CAACG,MAAM,IAAI;oBAAEA,QAAQ,IAAI,CAACH,MAAM,CAACG,MAAM;gBAAC,CAAC;gBACxD,GAAI,IAAI,CAACH,MAAM,CAACI,QAAQ,IAAI;oBAAEA,UAAU,IAAI,CAACJ,MAAM,CAACI,QAAQ;gBAAC,CAAC;gBAC9D,GAAI,IAAI,CAACJ,MAAM,CAACK,cAAc,IAAI;oBAAEA,gBAAgB,IAAI,CAACL,MAAM,CAACK,cAAc;gBAAC,CAAC;YAClF;YAEA,mCAAmC;YACnC,IAAIC;YAEJ,IAAI,IAAI,CAACN,MAAM,CAACO,MAAM,EAAE;gBACtB,wCAAwC;gBACxC,MAAMA,SAAS,IAAIC,cAAcC,MAAM,CAAC,IAAI,CAACT,MAAM,CAACO,MAAM;gBAC1DD,SAAS,MAAMtB,UAAUY,OAAOW,QAAQ;oBACtC,GAAGL,OAAO;oBACVQ,YAAY,IAAI,CAACV,MAAM,CAACU,UAAU,CAACC,MAAM,GAAG,IAAI,IAAI,CAACX,MAAM,CAACU,UAAU,GAAG;wBAAC;qBAAQ;gBACpF;YACF,OAAO,IAAI,IAAI,CAACE,YAAY,EAAE;gBAC5B,4CAA4C;gBAC5CN,SAAS,MAAMtB,UAAUY,OAAO,IAAI,CAACgB,YAAY,EAAE;oBACjD,GAAGV,OAAO;oBACVQ,YAAY,IAAI,CAACV,MAAM,CAACU,UAAU,CAACC,MAAM,GAAG,IAAI,IAAI,CAACX,MAAM,CAACU,UAAU,GAAG;wBAAC;wBAAS;qBAAQ;gBAC7F;YACF,OAAO,IAAI,IAAI,CAACV,MAAM,CAACa,SAAS,EAAE;gBAChC,oDAAoD;gBACpD,yDAAyD;gBACzD,MAAMC,MAAM,OAAO,IAAI,CAACd,MAAM,CAACa,SAAS,KAAK,WAAW,MAAM9B,WAAW,IAAI,CAACiB,MAAM,CAACa,SAAS,EAAE,WAAW,IAAI,CAACb,MAAM,CAACa,SAAS;gBAEhIP,SAAS,MAAMtB,UAAUY,OAAOkB,KAAK;oBACnC,GAAGZ,OAAO;oBACVQ,YAAY,IAAI,CAACV,MAAM,CAACU,UAAU,CAACC,MAAM,GAAG,IAAI,IAAI,CAACX,MAAM,CAACU,UAAU,GAAG;wBAAC;wBAAS;qBAAQ;gBAC7F;YACF,OAAO;gBACL,MAAM,IAAIlB,MAAM;YAClB;YAEA,OAAOc,OAAOT,OAAO;QACvB,EAAE,OAAOkB,OAAO;YACd,IAAIA,iBAAiBvB,OAAO;gBAC1B,MAAM,IAAIA,MAAM,CAAC,sCAAsC,EAAEuB,MAAMC,OAAO,EAAE;YAC1E;YACA,MAAM,IAAIxB,MAAM;QAClB;IACF;IAvHA,YAAYQ,MAAyB,CAAE;YAqBtBA,qBACDA,oBACIA;QAtBlB,yBAAyB;QACzB,IAAI,CAACA,OAAOO,MAAM,IAAI,CAACP,OAAOa,SAAS,IAAI,CAACb,OAAOiB,OAAO,EAAE;YAC1D,MAAM,IAAIzB,MAAM;QAClB;QAEA,IAAIQ,OAAOO,MAAM,IAAIP,OAAOO,MAAM,CAACI,MAAM,GAAG,IAAI;YAC9C,MAAM,IAAInB,MAAM;QAClB;QAEA,IAAI,AAACQ,CAAAA,OAAOO,MAAM,GAAG,IAAI,CAAA,IAAMP,CAAAA,OAAOa,SAAS,GAAG,IAAI,CAAA,IAAMb,CAAAA,OAAOiB,OAAO,GAAG,IAAI,CAAA,IAAK,GAAG;YACvF,MAAM,IAAIzB,MAAM;QAClB;QAEA,oCAAoC;QACpC,IAAI,CAACQ,MAAM,GAAG;YACZ,GAAIA,OAAOO,MAAM,KAAKW,aAAa;gBAAEX,QAAQP,OAAOO,MAAM;YAAC,CAAC;YAC5D,GAAIP,OAAOa,SAAS,KAAKK,aAAa;gBAAEL,WAAWb,OAAOa,SAAS;YAAC,CAAC;YACrE,GAAIb,OAAOiB,OAAO,KAAKC,aAAa;gBAAED,SAASjB,OAAOiB,OAAO;YAAC,CAAC;YAC/D,GAAIjB,OAAOG,MAAM,KAAKe,aAAa;gBAAEf,QAAQH,OAAOG,MAAM;YAAC,CAAC;YAC5D,GAAIH,OAAOI,QAAQ,KAAKc,aAAa;gBAAEd,UAAUJ,OAAOI,QAAQ;YAAC,CAAC;YAClEH,WAAW,GAAED,sBAAAA,OAAOC,WAAW,cAAlBD,iCAAAA,sBAAsB;YACnCU,UAAU,GAAEV,qBAAAA,OAAOU,UAAU,cAAjBV,gCAAAA,qBAAqB,EAAE;YACnCK,cAAc,GAAEL,yBAAAA,OAAOK,cAAc,cAArBL,oCAAAA,yBAAyB;QAC3C;QAEA,0CAA0C;QAC1C,IAAIA,OAAOiB,OAAO,EAAE;YAClB,IAAI,CAACL,YAAY,GAAG9B,mBAAmB,IAAIqC,IAAInB,OAAOiB,OAAO;QAC/D;IACF;AA0FF"}
@@ -1 +1 @@
1
- {"version":3,"sources":["/Users/kevin/Dev/Projects/ai/mcp-z/oauth/oauth/src/key-utils.ts"],"sourcesContent":["/**\n * Key generation utilities for consistent storage key format\n *\n * Key format: {accountId}:{service}:{type}\n * Example: work@gmail.com:gmail:token\n */\n\nimport type { Keyv } from 'keyv';\n\n/**\n * Key types for account-scoped data (requires accountId)\n */\nexport type AccountKeyType = 'token' | 'metadata' | 'dcr-client';\n\n/**\n * Key types for service-scoped data (no accountId)\n */\nexport type ServiceKeyType = 'active' | 'linked';\n\n/**\n * Parameters for account-scoped keys.\n * All fields are required - no silent defaults.\n */\nexport interface AccountKeyParams {\n /** Account identifier - typically an email address */\n accountId: string;\n\n /** Service name (e.g., 'gmail', 'sheets', 'drive', 'outlook') */\n service: string;\n}\n\n/**\n * Parameters for service-scoped keys.\n * These keys don't include accountId.\n */\nexport interface ServiceKeyParams {\n /** Service name */\n service: string;\n}\n\n/**\n * Validate key parameters don't contain colon delimiter\n */\nfunction validateKeyParams(params: AccountKeyParams | ServiceKeyParams): void {\n for (const [key, value] of Object.entries(params)) {\n if (typeof value !== 'string') {\n throw new Error(`Key parameter '${key}' must be a string, got: ${typeof value}`);\n }\n if (value.includes(':')) {\n throw new Error(`Key parameter '${key}' cannot contain colon character: ${value}`);\n }\n }\n}\n\n/**\n * Create account-scoped storage key.\n *\n * These keys are scoped to a specific account (email address) and store\n * account-specific data like OAuth tokens and account metadata.\n *\n * @param type - Key type ('token' for OAuth tokens, 'metadata' for account details, 'dcr-client' for DCR registration)\n * @param params - Account key parameters with explicit field names\n * @returns Storage key in format: \"{accountId}:{service}:{type}\"\n *\n * @example\n * ```typescript\n * // Store OAuth token\n * const tokenKey = createAccountKey('token', {\n * accountId: 'alice@gmail.com',\n * service: 'gmail'\n * });\n * // Returns: \"alice@gmail.com:gmail:token\"\n *\n * // Store account metadata (alias, timestamps, profile)\n * const metadataKey = createAccountKey('metadata', {\n * accountId: 'alice@gmail.com',\n * service: 'gmail'\n * });\n * // Returns: \"alice@gmail.com:gmail:metadata\"\n *\n * // Store DCR client registration info\n * const dcrKey = createAccountKey('dcr-client', {\n * accountId: 'alice@outlook.com',\n * service: 'outlook'\n * });\n * // Returns: \"alice@outlook.com:outlook:dcr-client\"\n * ```\n */\nexport function createAccountKey(type: AccountKeyType, params: AccountKeyParams): string {\n validateKeyParams(params);\n return `${params.accountId}:${params.service}:${type}`;\n}\n\n/**\n * Create service-scoped storage key.\n *\n * These keys are scoped to a service (not a specific account) and store\n * service-level data like which account is active or the list of linked accounts.\n *\n * @param type - Key type ('active' for active account, 'linked' for account list)\n * @param params - Service key parameters\n * @returns Storage key in format: \"{service}:{type}\"\n *\n * @example\n * ```typescript\n * // Track active account\n * const activeKey = createServiceKey('active', {\n * service: 'gmail'\n * });\n * // Returns: \"gmail:active\"\n *\n * // Store list of linked accounts\n * const linkedKey = createServiceKey('linked', {\n * service: 'gmail'\n * });\n * // Returns: \"gmail:linked\"\n * ```\n */\nexport function createServiceKey(type: ServiceKeyType, params: ServiceKeyParams): string {\n validateKeyParams(params);\n return `${params.service}:${type}`;\n}\n\n/**\n * Parse token key to extract components\n *\n * @param key - Storage key to parse\n * @returns Object with accountId and service, or undefined if invalid format\n *\n * @example\n * const parsed = parseTokenKey('user@gmail.com:gmail:token');\n * // Returns: { accountId: 'user@gmail.com', service: 'gmail' }\n *\n * const invalid = parseTokenKey('invalid-key');\n * // Returns: undefined\n */\nexport function parseTokenKey(key: string): { accountId: string; service: string } | undefined {\n const parts = key.split(':');\n if (parts.length !== 3 || parts[2] !== 'token' || !parts[0] || !parts[1]) {\n return undefined;\n }\n return {\n accountId: parts[0],\n service: parts[1],\n };\n}\n\n/**\n * List all account IDs for a service\n *\n * Iterates token keys and returns all accountIds that match the service.\n * Encapsulates key format details for forward compatibility.\n *\n * @param store - Keyv store to iterate\n * @param service - Service name\n * @returns Array of account IDs (e.g., email addresses)\n *\n * @example\n * const accounts = await listAccountIds(store, 'gmail');\n * // Returns: ['alice@gmail.com', 'bob@gmail.com']\n *\n * @example\n * // Empty array if no accounts found\n * const empty = await listAccountIds(store, 'unknown-service');\n * // Returns: []\n */\nexport async function listAccountIds(store: Keyv, service: string): Promise<string[]> {\n const accountIds: string[] = [];\n\n try {\n const iterator = store.iterator?.(undefined);\n if (!iterator) {\n return accountIds;\n }\n\n for await (const [key] of iterator) {\n const parsed = parseTokenKey(key);\n if (parsed && parsed.service === service) {\n accountIds.push(parsed.accountId);\n }\n }\n } catch (_error) {\n // If iteration fails, return empty array (fail gracefully)\n // This handles stores that don't support iteration\n return accountIds;\n }\n\n return accountIds;\n}\n"],"names":["validateKeyParams","params","key","value","Object","entries","Error","includes","createAccountKey","type","accountId","service","createServiceKey","parseTokenKey","parts","split","length","undefined","listAccountIds","store","accountIds","iterator","parsed","push","_error"],"mappings":"AAAA;;;;;CAKC,GAmCD;;CAEC,GACD,SAASA,kBAAkBC,MAA2C;IACpE,KAAK,MAAM,CAACC,KAAKC,MAAM,IAAIC,OAAOC,OAAO,CAACJ,QAAS;QACjD,IAAI,OAAOE,UAAU,UAAU;YAC7B,MAAM,IAAIG,MAAM,CAAC,eAAe,EAAEJ,IAAI,yBAAyB,EAAE,OAAOC,OAAO;QACjF;QACA,IAAIA,MAAMI,QAAQ,CAAC,MAAM;YACvB,MAAM,IAAID,MAAM,CAAC,eAAe,EAAEJ,IAAI,kCAAkC,EAAEC,OAAO;QACnF;IACF;AACF;AAEA;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;CAiCC,GACD,OAAO,SAASK,iBAAiBC,IAAoB,EAAER,MAAwB;IAC7ED,kBAAkBC;IAClB,OAAO,GAAGA,OAAOS,SAAS,CAAC,CAAC,EAAET,OAAOU,OAAO,CAAC,CAAC,EAAEF,MAAM;AACxD;AAEA;;;;;;;;;;;;;;;;;;;;;;;;CAwBC,GACD,OAAO,SAASG,iBAAiBH,IAAoB,EAAER,MAAwB;IAC7ED,kBAAkBC;IAClB,OAAO,GAAGA,OAAOU,OAAO,CAAC,CAAC,EAAEF,MAAM;AACpC;AAEA;;;;;;;;;;;;CAYC,GACD,OAAO,SAASI,cAAcX,GAAW;IACvC,MAAMY,QAAQZ,IAAIa,KAAK,CAAC;IACxB,IAAID,MAAME,MAAM,KAAK,KAAKF,KAAK,CAAC,EAAE,KAAK,WAAW,CAACA,KAAK,CAAC,EAAE,IAAI,CAACA,KAAK,CAAC,EAAE,EAAE;QACxE,OAAOG;IACT;IACA,OAAO;QACLP,WAAWI,KAAK,CAAC,EAAE;QACnBH,SAASG,KAAK,CAAC,EAAE;IACnB;AACF;AAEA;;;;;;;;;;;;;;;;;;CAkBC,GACD,OAAO,eAAeI,eAAeC,KAAW,EAAER,OAAe;IAC/D,MAAMS,aAAuB,EAAE;IAE/B,IAAI;YACeD;QAAjB,MAAME,YAAWF,kBAAAA,MAAME,QAAQ,cAAdF,sCAAAA,qBAAAA,OAAiBF;QAClC,IAAI,CAACI,UAAU;YACb,OAAOD;QACT;QAEA,WAAW,MAAM,CAAClB,IAAI,IAAImB,SAAU;YAClC,MAAMC,SAAST,cAAcX;YAC7B,IAAIoB,UAAUA,OAAOX,OAAO,KAAKA,SAAS;gBACxCS,WAAWG,IAAI,CAACD,OAAOZ,SAAS;YAClC;QACF;IACF,EAAE,OAAOc,QAAQ;QACf,2DAA2D;QAC3D,mDAAmD;QACnD,OAAOJ;IACT;IAEA,OAAOA;AACT"}
1
+ {"version":3,"sources":["/Users/kevin/Dev/Projects/mcp-z/oauth/src/key-utils.ts"],"sourcesContent":["/**\n * Key generation utilities for consistent storage key format\n *\n * Key format: {accountId}:{service}:{type}\n * Example: work@gmail.com:gmail:token\n */\n\nimport type { Keyv } from 'keyv';\n\n/**\n * Key types for account-scoped data (requires accountId)\n */\nexport type AccountKeyType = 'token' | 'metadata' | 'dcr-client';\n\n/**\n * Key types for service-scoped data (no accountId)\n */\nexport type ServiceKeyType = 'active' | 'linked';\n\n/**\n * Parameters for account-scoped keys.\n * All fields are required - no silent defaults.\n */\nexport interface AccountKeyParams {\n /** Account identifier - typically an email address */\n accountId: string;\n\n /** Service name (e.g., 'gmail', 'sheets', 'drive', 'outlook') */\n service: string;\n}\n\n/**\n * Parameters for service-scoped keys.\n * These keys don't include accountId.\n */\nexport interface ServiceKeyParams {\n /** Service name */\n service: string;\n}\n\n/**\n * Validate key parameters don't contain colon delimiter\n */\nfunction validateKeyParams(params: AccountKeyParams | ServiceKeyParams): void {\n for (const [key, value] of Object.entries(params)) {\n if (typeof value !== 'string') {\n throw new Error(`Key parameter '${key}' must be a string, got: ${typeof value}`);\n }\n if (value.includes(':')) {\n throw new Error(`Key parameter '${key}' cannot contain colon character: ${value}`);\n }\n }\n}\n\n/**\n * Create account-scoped storage key.\n *\n * These keys are scoped to a specific account (email address) and store\n * account-specific data like OAuth tokens and account metadata.\n *\n * @param type - Key type ('token' for OAuth tokens, 'metadata' for account details, 'dcr-client' for DCR registration)\n * @param params - Account key parameters with explicit field names\n * @returns Storage key in format: \"{accountId}:{service}:{type}\"\n *\n * @example\n * ```typescript\n * // Store OAuth token\n * const tokenKey = createAccountKey('token', {\n * accountId: 'alice@gmail.com',\n * service: 'gmail'\n * });\n * // Returns: \"alice@gmail.com:gmail:token\"\n *\n * // Store account metadata (alias, timestamps, profile)\n * const metadataKey = createAccountKey('metadata', {\n * accountId: 'alice@gmail.com',\n * service: 'gmail'\n * });\n * // Returns: \"alice@gmail.com:gmail:metadata\"\n *\n * // Store DCR client registration info\n * const dcrKey = createAccountKey('dcr-client', {\n * accountId: 'alice@outlook.com',\n * service: 'outlook'\n * });\n * // Returns: \"alice@outlook.com:outlook:dcr-client\"\n * ```\n */\nexport function createAccountKey(type: AccountKeyType, params: AccountKeyParams): string {\n validateKeyParams(params);\n return `${params.accountId}:${params.service}:${type}`;\n}\n\n/**\n * Create service-scoped storage key.\n *\n * These keys are scoped to a service (not a specific account) and store\n * service-level data like which account is active or the list of linked accounts.\n *\n * @param type - Key type ('active' for active account, 'linked' for account list)\n * @param params - Service key parameters\n * @returns Storage key in format: \"{service}:{type}\"\n *\n * @example\n * ```typescript\n * // Track active account\n * const activeKey = createServiceKey('active', {\n * service: 'gmail'\n * });\n * // Returns: \"gmail:active\"\n *\n * // Store list of linked accounts\n * const linkedKey = createServiceKey('linked', {\n * service: 'gmail'\n * });\n * // Returns: \"gmail:linked\"\n * ```\n */\nexport function createServiceKey(type: ServiceKeyType, params: ServiceKeyParams): string {\n validateKeyParams(params);\n return `${params.service}:${type}`;\n}\n\n/**\n * Parse token key to extract components\n *\n * @param key - Storage key to parse\n * @returns Object with accountId and service, or undefined if invalid format\n *\n * @example\n * const parsed = parseTokenKey('user@gmail.com:gmail:token');\n * // Returns: { accountId: 'user@gmail.com', service: 'gmail' }\n *\n * const invalid = parseTokenKey('invalid-key');\n * // Returns: undefined\n */\nexport function parseTokenKey(key: string): { accountId: string; service: string } | undefined {\n const parts = key.split(':');\n if (parts.length !== 3 || parts[2] !== 'token' || !parts[0] || !parts[1]) {\n return undefined;\n }\n return {\n accountId: parts[0],\n service: parts[1],\n };\n}\n\n/**\n * List all account IDs for a service\n *\n * Iterates token keys and returns all accountIds that match the service.\n * Encapsulates key format details for forward compatibility.\n *\n * @param store - Keyv store to iterate\n * @param service - Service name\n * @returns Array of account IDs (e.g., email addresses)\n *\n * @example\n * const accounts = await listAccountIds(store, 'gmail');\n * // Returns: ['alice@gmail.com', 'bob@gmail.com']\n *\n * @example\n * // Empty array if no accounts found\n * const empty = await listAccountIds(store, 'unknown-service');\n * // Returns: []\n */\nexport async function listAccountIds(store: Keyv, service: string): Promise<string[]> {\n const accountIds: string[] = [];\n\n try {\n const iterator = store.iterator?.(undefined);\n if (!iterator) {\n return accountIds;\n }\n\n for await (const [key] of iterator) {\n const parsed = parseTokenKey(key);\n if (parsed && parsed.service === service) {\n accountIds.push(parsed.accountId);\n }\n }\n } catch (_error) {\n // If iteration fails, return empty array (fail gracefully)\n // This handles stores that don't support iteration\n return accountIds;\n }\n\n return accountIds;\n}\n"],"names":["validateKeyParams","params","key","value","Object","entries","Error","includes","createAccountKey","type","accountId","service","createServiceKey","parseTokenKey","parts","split","length","undefined","listAccountIds","store","accountIds","iterator","parsed","push","_error"],"mappings":"AAAA;;;;;CAKC,GAmCD;;CAEC,GACD,SAASA,kBAAkBC,MAA2C;IACpE,KAAK,MAAM,CAACC,KAAKC,MAAM,IAAIC,OAAOC,OAAO,CAACJ,QAAS;QACjD,IAAI,OAAOE,UAAU,UAAU;YAC7B,MAAM,IAAIG,MAAM,CAAC,eAAe,EAAEJ,IAAI,yBAAyB,EAAE,OAAOC,OAAO;QACjF;QACA,IAAIA,MAAMI,QAAQ,CAAC,MAAM;YACvB,MAAM,IAAID,MAAM,CAAC,eAAe,EAAEJ,IAAI,kCAAkC,EAAEC,OAAO;QACnF;IACF;AACF;AAEA;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;CAiCC,GACD,OAAO,SAASK,iBAAiBC,IAAoB,EAAER,MAAwB;IAC7ED,kBAAkBC;IAClB,OAAO,GAAGA,OAAOS,SAAS,CAAC,CAAC,EAAET,OAAOU,OAAO,CAAC,CAAC,EAAEF,MAAM;AACxD;AAEA;;;;;;;;;;;;;;;;;;;;;;;;CAwBC,GACD,OAAO,SAASG,iBAAiBH,IAAoB,EAAER,MAAwB;IAC7ED,kBAAkBC;IAClB,OAAO,GAAGA,OAAOU,OAAO,CAAC,CAAC,EAAEF,MAAM;AACpC;AAEA;;;;;;;;;;;;CAYC,GACD,OAAO,SAASI,cAAcX,GAAW;IACvC,MAAMY,QAAQZ,IAAIa,KAAK,CAAC;IACxB,IAAID,MAAME,MAAM,KAAK,KAAKF,KAAK,CAAC,EAAE,KAAK,WAAW,CAACA,KAAK,CAAC,EAAE,IAAI,CAACA,KAAK,CAAC,EAAE,EAAE;QACxE,OAAOG;IACT;IACA,OAAO;QACLP,WAAWI,KAAK,CAAC,EAAE;QACnBH,SAASG,KAAK,CAAC,EAAE;IACnB;AACF;AAEA;;;;;;;;;;;;;;;;;;CAkBC,GACD,OAAO,eAAeI,eAAeC,KAAW,EAAER,OAAe;IAC/D,MAAMS,aAAuB,EAAE;IAE/B,IAAI;YACeD;QAAjB,MAAME,YAAWF,kBAAAA,MAAME,QAAQ,cAAdF,sCAAAA,qBAAAA,OAAiBF;QAClC,IAAI,CAACI,UAAU;YACb,OAAOD;QACT;QAEA,WAAW,MAAM,CAAClB,IAAI,IAAImB,SAAU;YAClC,MAAMC,SAAST,cAAcX;YAC7B,IAAIoB,UAAUA,OAAOX,OAAO,KAAKA,SAAS;gBACxCS,WAAWG,IAAI,CAACD,OAAOZ,SAAS;YAClC;QACF;IACF,EAAE,OAAOc,QAAQ;QACf,2DAA2D;QAC3D,mDAAmD;QACnD,OAAOJ;IACT;IAEA,OAAOA;AACT"}
@@ -1 +1 @@
1
- {"version":3,"sources":["/Users/kevin/Dev/Projects/ai/mcp-z/oauth/oauth/src/lib/account-server/index.ts"],"sourcesContent":["/**\n * Unified account management API for MCP servers.\n *\n * Provides two account management modes:\n * - Loopback: Server-managed multi-account OAuth (LoopbackOAuthProvider)\n * - Stateless: MCP client-managed OAuth (read-only status)\n *\n * @example\n * // Loopback OAuth account management\n * const {tools, prompts} = AccountServer.createLoopback({\n * service: 'gmail',\n * store: tokenStore,\n * logger,\n * auth: authProvider\n * });\n *\n * @example\n * // Stateless mode (MCP OAuth)\n * const {tools, prompts} = AccountServer.createStateless({\n * service: 'gmail'\n * });\n */\n\nimport { createLoopback } from './loopback.ts';\nimport { createStateless } from './stateless.ts';\n\nexport const AccountServer = {\n /**\n * Create loopback OAuth account management tools.\n * Server manages multiple accounts with stored tokens (LoopbackOAuthProvider).\n * Returns 4 tools: account-me, account-switch, account-remove, account-list.\n * No prompts.\n */\n createLoopback,\n\n /**\n * Create stateless mode tools.\n * MCP client manages authentication. Server extracts user identity from bearer token.\n * Returns 1 tool: account-me.\n * No prompts.\n */\n createStateless,\n};\n\nexport { createLoopback } from './loopback.ts';\nexport { createAccountMe } from './me.ts';\nexport { findAccountByEmailOrAlias } from './shared-utils.ts';\nexport { createStateless } from './stateless.ts';\nexport type { AccountLoopbackConfig, AccountMeConfig, AccountStatelessConfig } from './types.ts';\n"],"names":["createLoopback","createStateless","AccountServer","createAccountMe","findAccountByEmailOrAlias"],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;CAqBC,GAED,SAASA,cAAc,QAAQ,gBAAgB;AAC/C,SAASC,eAAe,QAAQ,iBAAiB;AAEjD,OAAO,MAAMC,gBAAgB;IAC3B;;;;;GAKC,GACDF;IAEA;;;;;GAKC,GACDC;AACF,EAAE;AAEF,SAASD,cAAc,QAAQ,gBAAgB;AAC/C,SAASG,eAAe,QAAQ,UAAU;AAC1C,SAASC,yBAAyB,QAAQ,oBAAoB;AAC9D,SAASH,eAAe,QAAQ,iBAAiB"}
1
+ {"version":3,"sources":["/Users/kevin/Dev/Projects/mcp-z/oauth/src/lib/account-server/index.ts"],"sourcesContent":["/**\n * Unified account management API for MCP servers.\n *\n * Provides two account management modes:\n * - Loopback: Server-managed multi-account OAuth (LoopbackOAuthProvider)\n * - Stateless: MCP client-managed OAuth (read-only status)\n *\n * @example\n * // Loopback OAuth account management\n * const {tools, prompts} = AccountServer.createLoopback({\n * service: 'gmail',\n * store: tokenStore,\n * logger,\n * auth: authProvider\n * });\n *\n * @example\n * // Stateless mode (MCP OAuth)\n * const {tools, prompts} = AccountServer.createStateless({\n * service: 'gmail'\n * });\n */\n\nimport { createLoopback } from './loopback.ts';\nimport { createStateless } from './stateless.ts';\n\nexport const AccountServer = {\n /**\n * Create loopback OAuth account management tools.\n * Server manages multiple accounts with stored tokens (LoopbackOAuthProvider).\n * Returns 4 tools: account-me, account-switch, account-remove, account-list.\n * No prompts.\n */\n createLoopback,\n\n /**\n * Create stateless mode tools.\n * MCP client manages authentication. Server extracts user identity from bearer token.\n * Returns 1 tool: account-me.\n * No prompts.\n */\n createStateless,\n};\n\nexport { createLoopback } from './loopback.ts';\nexport { createAccountMe } from './me.ts';\nexport { findAccountByEmailOrAlias } from './shared-utils.ts';\nexport { createStateless } from './stateless.ts';\nexport type { AccountLoopbackConfig, AccountMeConfig, AccountStatelessConfig } from './types.ts';\n"],"names":["createLoopback","createStateless","AccountServer","createAccountMe","findAccountByEmailOrAlias"],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;CAqBC,GAED,SAASA,cAAc,QAAQ,gBAAgB;AAC/C,SAASC,eAAe,QAAQ,iBAAiB;AAEjD,OAAO,MAAMC,gBAAgB;IAC3B;;;;;GAKC,GACDF;IAEA;;;;;GAKC,GACDC;AACF,EAAE;AAEF,SAASD,cAAc,QAAQ,gBAAgB;AAC/C,SAASG,eAAe,QAAQ,UAAU;AAC1C,SAASC,yBAAyB,QAAQ,oBAAoB;AAC9D,SAASH,eAAe,QAAQ,iBAAiB"}
@@ -10,6 +10,7 @@
10
10
  * - account-remove: Remove account and delete tokens
11
11
  * - account-list: Show all linked accounts (returns empty array if none)
12
12
  */ import { ErrorCode, McpError } from '@modelcontextprotocol/sdk/types.js';
13
+ import { randomUUID } from 'crypto';
13
14
  import { z } from 'zod';
14
15
  import { addAccount, getAccountInfo, getActiveAccount, getLinkedAccounts, removeAccount, setAccountInfo, setActiveAccount } from '../../account-utils.js';
15
16
  import { createAccountMe } from './me.js';
@@ -115,12 +116,14 @@ import { findAccountByEmailOrAlias } from './shared-utils.js';
115
116
  }
116
117
  }
117
118
  // Not linked or no email provided - trigger OAuth flow for new account
118
- // Check if provider supports interactive authentication
119
- if (!auth.authenticateNewAccount) {
120
- throw new Error('Account switching requires interactive authentication. ' + 'The current auth provider does not support authenticateNewAccount().');
119
+ // Force an OAuth flow by passing a unique accountId to bypass any active account.
120
+ await auth.getAccessToken(`new:${randomUUID()}`);
121
+ email = await getActiveAccount(store, {
122
+ service
123
+ });
124
+ if (!email) {
125
+ throw new Error('OAuth flow completed without setting an active account');
121
126
  }
122
- // Trigger new authentication with account selection
123
- email = await auth.authenticateNewAccount();
124
127
  // Check if account already exists (in case OAuth returned different email than requested)
125
128
  isNew = !existingAccounts.includes(email);
126
129
  if (isNew) {