File size: 8,970 Bytes
064bfd6 | 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 224 225 226 227 228 229 230 231 232 233 234 235 | 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
}
|