import { isEnvTruthy } from 'src/utils/envUtils.js' // Default to prod config, override with test/staging if enabled type OauthConfigType = 'prod' | 'staging' | 'local' function getOauthConfigType(): OauthConfigType { if (process.env.USER_TYPE === 'ant') { if (isEnvTruthy(process.env.USE_LOCAL_OAUTH)) { return 'local' } if (isEnvTruthy(process.env.USE_STAGING_OAUTH)) { return 'staging' } } return 'prod' } export function fileSuffixForOauthConfig(): string { if (process.env.CLAUDE_CODE_CUSTOM_OAUTH_URL) { return '-custom-oauth' } switch (getOauthConfigType()) { case 'local': return '-local-oauth' case 'staging': return '-staging-oauth' case 'prod': // No suffix for production config return '' } } export const CLAUDE_AI_INFERENCE_SCOPE = 'user:inference' as const export const CLAUDE_AI_PROFILE_SCOPE = 'user:profile' as const const CONSOLE_SCOPE = 'org:create_api_key' as const export const OAUTH_BETA_HEADER = 'oauth-2025-04-20' as const // Console OAuth scopes - for API key creation via Console export const CONSOLE_OAUTH_SCOPES = [ CONSOLE_SCOPE, CLAUDE_AI_PROFILE_SCOPE, ] as const // Claude.ai OAuth scopes - for Claude.ai subscribers (Pro/Max/Team/Enterprise) export const CLAUDE_AI_OAUTH_SCOPES = [ CLAUDE_AI_PROFILE_SCOPE, CLAUDE_AI_INFERENCE_SCOPE, 'user:sessions:claude_code', 'user:mcp_servers', 'user:file_upload', ] as const // All OAuth scopes - union of all scopes used in Claude CLI // When logging in, request all scopes in order to handle both Console -> Claude.ai redirect // Ensure that `OAuthConsentPage` in apps repo is kept in sync with this list. export const ALL_OAUTH_SCOPES = Array.from( new Set([...CONSOLE_OAUTH_SCOPES, ...CLAUDE_AI_OAUTH_SCOPES]), ) type OauthConfig = { BASE_API_URL: string CONSOLE_AUTHORIZE_URL: string CLAUDE_AI_AUTHORIZE_URL: string /** * The claude.ai web origin. Separate from CLAUDE_AI_AUTHORIZE_URL because * that now routes through claude.com/cai/* for attribution — deriving * .origin from it would give claude.com, breaking links to /code, * /settings/connectors, and other claude.ai web pages. */ CLAUDE_AI_ORIGIN: string TOKEN_URL: string API_KEY_URL: string ROLES_URL: string CONSOLE_SUCCESS_URL: string CLAUDEAI_SUCCESS_URL: string MANUAL_REDIRECT_URL: string CLIENT_ID: string OAUTH_FILE_SUFFIX: string MCP_PROXY_URL: string MCP_PROXY_PATH: string } // Production OAuth configuration - Used in normal operation const PROD_OAUTH_CONFIG = { BASE_API_URL: 'https://api.anthropic.com', CONSOLE_AUTHORIZE_URL: 'https://platform.claude.com/oauth/authorize', // Bounces through claude.com/cai/* so CLI sign-ins connect to claude.com // visits for attribution. 307s to claude.ai/oauth/authorize in two hops. CLAUDE_AI_AUTHORIZE_URL: 'https://claude.com/cai/oauth/authorize', CLAUDE_AI_ORIGIN: 'https://claude.ai', TOKEN_URL: 'https://platform.claude.com/v1/oauth/token', API_KEY_URL: 'https://api.anthropic.com/api/oauth/claude_cli/create_api_key', ROLES_URL: 'https://api.anthropic.com/api/oauth/claude_cli/roles', CONSOLE_SUCCESS_URL: 'https://platform.claude.com/buy_credits?returnUrl=/oauth/code/success%3Fapp%3Dclaude-code', CLAUDEAI_SUCCESS_URL: 'https://platform.claude.com/oauth/code/success?app=claude-code', MANUAL_REDIRECT_URL: 'https://platform.claude.com/oauth/code/callback', CLIENT_ID: '9d1c250a-e61b-44d9-88ed-5944d1962f5e', // No suffix for production config OAUTH_FILE_SUFFIX: '', MCP_PROXY_URL: 'https://mcp-proxy.anthropic.com', MCP_PROXY_PATH: '/v1/mcp/{server_id}', } as const /** * Client ID Metadata Document URL for MCP OAuth (CIMD / SEP-991). * When an MCP auth server advertises client_id_metadata_document_supported: true, * Claude Code uses this URL as its client_id instead of Dynamic Client Registration. * The URL must point to a JSON document hosted by Anthropic. * See: https://datatracker.ietf.org/doc/html/draft-ietf-oauth-client-id-metadata-document-00 */ export const MCP_CLIENT_METADATA_URL = 'https://claude.ai/oauth/claude-code-client-metadata' // Staging OAuth configuration - only included in ant builds with staging flag // Uses literal check for dead code elimination const STAGING_OAUTH_CONFIG = process.env.USER_TYPE === 'ant' ? ({ BASE_API_URL: 'https://api-staging.anthropic.com', CONSOLE_AUTHORIZE_URL: 'https://platform.staging.ant.dev/oauth/authorize', CLAUDE_AI_AUTHORIZE_URL: 'https://claude-ai.staging.ant.dev/oauth/authorize', CLAUDE_AI_ORIGIN: 'https://claude-ai.staging.ant.dev', TOKEN_URL: 'https://platform.staging.ant.dev/v1/oauth/token', API_KEY_URL: 'https://api-staging.anthropic.com/api/oauth/claude_cli/create_api_key', ROLES_URL: 'https://api-staging.anthropic.com/api/oauth/claude_cli/roles', CONSOLE_SUCCESS_URL: 'https://platform.staging.ant.dev/buy_credits?returnUrl=/oauth/code/success%3Fapp%3Dclaude-code', CLAUDEAI_SUCCESS_URL: 'https://platform.staging.ant.dev/oauth/code/success?app=claude-code', MANUAL_REDIRECT_URL: 'https://platform.staging.ant.dev/oauth/code/callback', CLIENT_ID: '22422756-60c9-4084-8eb7-27705fd5cf9a', OAUTH_FILE_SUFFIX: '-staging-oauth', MCP_PROXY_URL: 'https://mcp-proxy-staging.anthropic.com', MCP_PROXY_PATH: '/v1/mcp/{server_id}', } as const) : undefined // Three local dev servers: :8000 api-proxy (`api dev start -g ccr`), // :4000 claude-ai frontend, :3000 Console frontend. Env vars let // scripts/claude-localhost override if your layout differs. function getLocalOauthConfig(): OauthConfig { const api = process.env.CLAUDE_LOCAL_OAUTH_API_BASE?.replace(/\/$/, '') ?? 'http://localhost:8000' const apps = process.env.CLAUDE_LOCAL_OAUTH_APPS_BASE?.replace(/\/$/, '') ?? 'http://localhost:4000' const consoleBase = process.env.CLAUDE_LOCAL_OAUTH_CONSOLE_BASE?.replace(/\/$/, '') ?? 'http://localhost:3000' return { BASE_API_URL: api, CONSOLE_AUTHORIZE_URL: `${consoleBase}/oauth/authorize`, CLAUDE_AI_AUTHORIZE_URL: `${apps}/oauth/authorize`, CLAUDE_AI_ORIGIN: apps, TOKEN_URL: `${api}/v1/oauth/token`, API_KEY_URL: `${api}/api/oauth/claude_cli/create_api_key`, ROLES_URL: `${api}/api/oauth/claude_cli/roles`, CONSOLE_SUCCESS_URL: `${consoleBase}/buy_credits?returnUrl=/oauth/code/success%3Fapp%3Dclaude-code`, CLAUDEAI_SUCCESS_URL: `${consoleBase}/oauth/code/success?app=claude-code`, MANUAL_REDIRECT_URL: `${consoleBase}/oauth/code/callback`, CLIENT_ID: '22422756-60c9-4084-8eb7-27705fd5cf9a', OAUTH_FILE_SUFFIX: '-local-oauth', MCP_PROXY_URL: 'http://localhost:8205', MCP_PROXY_PATH: '/v1/toolbox/shttp/mcp/{server_id}', } } // Allowed base URLs for CLAUDE_CODE_CUSTOM_OAUTH_URL override. // Only FedStart/PubSec deployments are permitted to prevent OAuth tokens // from being sent to arbitrary endpoints. const ALLOWED_OAUTH_BASE_URLS = [ 'https://beacon.claude-ai.staging.ant.dev', 'https://claude.fedstart.com', 'https://claude-staging.fedstart.com', ] // Default to prod config, override with test/staging if enabled export function getOauthConfig(): OauthConfig { let config: OauthConfig = (() => { switch (getOauthConfigType()) { case 'local': return getLocalOauthConfig() case 'staging': return STAGING_OAUTH_CONFIG ?? PROD_OAUTH_CONFIG case 'prod': return PROD_OAUTH_CONFIG } })() // Allow overriding all OAuth URLs to point to an approved FedStart deployment. // Only allowlisted base URLs are accepted to prevent credential leakage. const oauthBaseUrl = process.env.CLAUDE_CODE_CUSTOM_OAUTH_URL if (oauthBaseUrl) { const base = oauthBaseUrl.replace(/\/$/, '') if (!ALLOWED_OAUTH_BASE_URLS.includes(base)) { throw new Error( 'CLAUDE_CODE_CUSTOM_OAUTH_URL is not an approved endpoint.', ) } config = { ...config, BASE_API_URL: base, CONSOLE_AUTHORIZE_URL: `${base}/oauth/authorize`, CLAUDE_AI_AUTHORIZE_URL: `${base}/oauth/authorize`, CLAUDE_AI_ORIGIN: base, TOKEN_URL: `${base}/v1/oauth/token`, API_KEY_URL: `${base}/api/oauth/claude_cli/create_api_key`, ROLES_URL: `${base}/api/oauth/claude_cli/roles`, CONSOLE_SUCCESS_URL: `${base}/oauth/code/success?app=claude-code`, CLAUDEAI_SUCCESS_URL: `${base}/oauth/code/success?app=claude-code`, MANUAL_REDIRECT_URL: `${base}/oauth/code/callback`, OAUTH_FILE_SUFFIX: '-custom-oauth', } } // Allow CLIENT_ID override via environment variable (e.g., for Xcode integration) const clientIdOverride = process.env.CLAUDE_CODE_OAUTH_CLIENT_ID if (clientIdOverride) { config = { ...config, CLIENT_ID: clientIdOverride, } } return config }