(Alleged) Leaked source of Claude Code
0
fork

Configure Feed

Select the types of activity you want to include in your feed.

at main 330 lines 11 kB view raw
1/* eslint-disable custom-rules/no-process-exit -- CLI subcommand handler intentionally exits */ 2 3import { 4 clearAuthRelatedCaches, 5 performLogout, 6} from '../../commands/logout/logout.js' 7import { 8 type AnalyticsMetadata_I_VERIFIED_THIS_IS_NOT_CODE_OR_FILEPATHS, 9 logEvent, 10} from '../../services/analytics/index.js' 11import { getSSLErrorHint } from '../../services/api/errorUtils.js' 12import { fetchAndStoreClaudeCodeFirstTokenDate } from '../../services/api/firstTokenDate.js' 13import { 14 createAndStoreApiKey, 15 fetchAndStoreUserRoles, 16 refreshOAuthToken, 17 shouldUseClaudeAIAuth, 18 storeOAuthAccountInfo, 19} from '../../services/oauth/client.js' 20import { getOauthProfileFromOauthToken } from '../../services/oauth/getOauthProfile.js' 21import { OAuthService } from '../../services/oauth/index.js' 22import type { OAuthTokens } from '../../services/oauth/types.js' 23import { 24 clearOAuthTokenCache, 25 getAnthropicApiKeyWithSource, 26 getAuthTokenSource, 27 getOauthAccountInfo, 28 getSubscriptionType, 29 isUsing3PServices, 30 saveOAuthTokensIfNeeded, 31 validateForceLoginOrg, 32} from '../../utils/auth.js' 33import { saveGlobalConfig } from '../../utils/config.js' 34import { logForDebugging } from '../../utils/debug.js' 35import { isRunningOnHomespace } from '../../utils/envUtils.js' 36import { errorMessage } from '../../utils/errors.js' 37import { logError } from '../../utils/log.js' 38import { getAPIProvider } from '../../utils/model/providers.js' 39import { getInitialSettings } from '../../utils/settings/settings.js' 40import { jsonStringify } from '../../utils/slowOperations.js' 41import { 42 buildAccountProperties, 43 buildAPIProviderProperties, 44} from '../../utils/status.js' 45 46/** 47 * Shared post-token-acquisition logic. Saves tokens, fetches profile/roles, 48 * and sets up the local auth state. 49 */ 50export async function installOAuthTokens(tokens: OAuthTokens): Promise<void> { 51 // Clear old state before saving new credentials 52 await performLogout({ clearOnboarding: false }) 53 54 // Reuse pre-fetched profile if available, otherwise fetch fresh 55 const profile = 56 tokens.profile ?? (await getOauthProfileFromOauthToken(tokens.accessToken)) 57 if (profile) { 58 storeOAuthAccountInfo({ 59 accountUuid: profile.account.uuid, 60 emailAddress: profile.account.email, 61 organizationUuid: profile.organization.uuid, 62 displayName: profile.account.display_name || undefined, 63 hasExtraUsageEnabled: 64 profile.organization.has_extra_usage_enabled ?? undefined, 65 billingType: profile.organization.billing_type ?? undefined, 66 subscriptionCreatedAt: 67 profile.organization.subscription_created_at ?? undefined, 68 accountCreatedAt: profile.account.created_at, 69 }) 70 } else if (tokens.tokenAccount) { 71 // Fallback to token exchange account data when profile endpoint fails 72 storeOAuthAccountInfo({ 73 accountUuid: tokens.tokenAccount.uuid, 74 emailAddress: tokens.tokenAccount.emailAddress, 75 organizationUuid: tokens.tokenAccount.organizationUuid, 76 }) 77 } 78 79 const storageResult = saveOAuthTokensIfNeeded(tokens) 80 clearOAuthTokenCache() 81 82 if (storageResult.warning) { 83 logEvent('tengu_oauth_storage_warning', { 84 warning: 85 storageResult.warning as AnalyticsMetadata_I_VERIFIED_THIS_IS_NOT_CODE_OR_FILEPATHS, 86 }) 87 } 88 89 // Roles and first-token-date may fail for limited-scope tokens (e.g. 90 // inference-only from setup-token). They're not required for core auth. 91 await fetchAndStoreUserRoles(tokens.accessToken).catch(err => 92 logForDebugging(String(err), { level: 'error' }), 93 ) 94 95 if (shouldUseClaudeAIAuth(tokens.scopes)) { 96 await fetchAndStoreClaudeCodeFirstTokenDate().catch(err => 97 logForDebugging(String(err), { level: 'error' }), 98 ) 99 } else { 100 // API key creation is critical for Console users — let it throw. 101 const apiKey = await createAndStoreApiKey(tokens.accessToken) 102 if (!apiKey) { 103 throw new Error( 104 'Unable to create API key. The server accepted the request but did not return a key.', 105 ) 106 } 107 } 108 109 await clearAuthRelatedCaches() 110} 111 112export async function authLogin({ 113 email, 114 sso, 115 console: useConsole, 116 claudeai, 117}: { 118 email?: string 119 sso?: boolean 120 console?: boolean 121 claudeai?: boolean 122}): Promise<void> { 123 if (useConsole && claudeai) { 124 process.stderr.write( 125 'Error: --console and --claudeai cannot be used together.\n', 126 ) 127 process.exit(1) 128 } 129 130 const settings = getInitialSettings() 131 // forceLoginMethod is a hard constraint (enterprise setting) — matches ConsoleOAuthFlow behavior. 132 // Without it, --console selects Console; --claudeai (or no flag) selects claude.ai. 133 const loginWithClaudeAi = settings.forceLoginMethod 134 ? settings.forceLoginMethod === 'claudeai' 135 : !useConsole 136 const orgUUID = settings.forceLoginOrgUUID 137 138 // Fast path: if a refresh token is provided via env var, skip the browser 139 // OAuth flow and exchange it directly for tokens. 140 const envRefreshToken = process.env.CLAUDE_CODE_OAUTH_REFRESH_TOKEN 141 if (envRefreshToken) { 142 const envScopes = process.env.CLAUDE_CODE_OAUTH_SCOPES 143 if (!envScopes) { 144 process.stderr.write( 145 'CLAUDE_CODE_OAUTH_SCOPES is required when using CLAUDE_CODE_OAUTH_REFRESH_TOKEN.\n' + 146 'Set it to the space-separated scopes the refresh token was issued with\n' + 147 '(e.g. "user:inference" or "user:profile user:inference user:sessions:claude_code user:mcp_servers").\n', 148 ) 149 process.exit(1) 150 } 151 152 const scopes = envScopes.split(/\s+/).filter(Boolean) 153 154 try { 155 logEvent('tengu_login_from_refresh_token', {}) 156 157 const tokens = await refreshOAuthToken(envRefreshToken, { scopes }) 158 await installOAuthTokens(tokens) 159 160 const orgResult = await validateForceLoginOrg() 161 if (!orgResult.valid) { 162 process.stderr.write(orgResult.message + '\n') 163 process.exit(1) 164 } 165 166 // Mark onboarding complete — interactive paths handle this via 167 // the Onboarding component, but the env var path skips it. 168 saveGlobalConfig(current => { 169 if (current.hasCompletedOnboarding) return current 170 return { ...current, hasCompletedOnboarding: true } 171 }) 172 173 logEvent('tengu_oauth_success', { 174 loginWithClaudeAi: shouldUseClaudeAIAuth(tokens.scopes), 175 }) 176 process.stdout.write('Login successful.\n') 177 process.exit(0) 178 } catch (err) { 179 logError(err) 180 const sslHint = getSSLErrorHint(err) 181 process.stderr.write( 182 `Login failed: ${errorMessage(err)}\n${sslHint ? sslHint + '\n' : ''}`, 183 ) 184 process.exit(1) 185 } 186 } 187 188 const resolvedLoginMethod = sso ? 'sso' : undefined 189 190 const oauthService = new OAuthService() 191 192 try { 193 logEvent('tengu_oauth_flow_start', { loginWithClaudeAi }) 194 195 const result = await oauthService.startOAuthFlow( 196 async url => { 197 process.stdout.write('Opening browser to sign in…\n') 198 process.stdout.write(`If the browser didn't open, visit: ${url}\n`) 199 }, 200 { 201 loginWithClaudeAi, 202 loginHint: email, 203 loginMethod: resolvedLoginMethod, 204 orgUUID, 205 }, 206 ) 207 208 await installOAuthTokens(result) 209 210 const orgResult = await validateForceLoginOrg() 211 if (!orgResult.valid) { 212 process.stderr.write(orgResult.message + '\n') 213 process.exit(1) 214 } 215 216 logEvent('tengu_oauth_success', { loginWithClaudeAi }) 217 218 process.stdout.write('Login successful.\n') 219 process.exit(0) 220 } catch (err) { 221 logError(err) 222 const sslHint = getSSLErrorHint(err) 223 process.stderr.write( 224 `Login failed: ${errorMessage(err)}\n${sslHint ? sslHint + '\n' : ''}`, 225 ) 226 process.exit(1) 227 } finally { 228 oauthService.cleanup() 229 } 230} 231 232export async function authStatus(opts: { 233 json?: boolean 234 text?: boolean 235}): Promise<void> { 236 const { source: authTokenSource, hasToken } = getAuthTokenSource() 237 const { source: apiKeySource } = getAnthropicApiKeyWithSource() 238 const hasApiKeyEnvVar = 239 !!process.env.ANTHROPIC_API_KEY && !isRunningOnHomespace() 240 const oauthAccount = getOauthAccountInfo() 241 const subscriptionType = getSubscriptionType() 242 const using3P = isUsing3PServices() 243 const loggedIn = 244 hasToken || apiKeySource !== 'none' || hasApiKeyEnvVar || using3P 245 246 // Determine auth method 247 let authMethod: string = 'none' 248 if (using3P) { 249 authMethod = 'third_party' 250 } else if (authTokenSource === 'claude.ai') { 251 authMethod = 'claude.ai' 252 } else if (authTokenSource === 'apiKeyHelper') { 253 authMethod = 'api_key_helper' 254 } else if (authTokenSource !== 'none') { 255 authMethod = 'oauth_token' 256 } else if (apiKeySource === 'ANTHROPIC_API_KEY' || hasApiKeyEnvVar) { 257 authMethod = 'api_key' 258 } else if (apiKeySource === '/login managed key') { 259 authMethod = 'claude.ai' 260 } 261 262 if (opts.text) { 263 const properties = [ 264 ...buildAccountProperties(), 265 ...buildAPIProviderProperties(), 266 ] 267 let hasAuthProperty = false 268 for (const prop of properties) { 269 const value = 270 typeof prop.value === 'string' 271 ? prop.value 272 : Array.isArray(prop.value) 273 ? prop.value.join(', ') 274 : null 275 if (value === null || value === 'none') { 276 continue 277 } 278 hasAuthProperty = true 279 if (prop.label) { 280 process.stdout.write(`${prop.label}: ${value}\n`) 281 } else { 282 process.stdout.write(`${value}\n`) 283 } 284 } 285 if (!hasAuthProperty && hasApiKeyEnvVar) { 286 process.stdout.write('API key: ANTHROPIC_API_KEY\n') 287 } 288 if (!loggedIn) { 289 process.stdout.write( 290 'Not logged in. Run claude auth login to authenticate.\n', 291 ) 292 } 293 } else { 294 const apiProvider = getAPIProvider() 295 const resolvedApiKeySource = 296 apiKeySource !== 'none' 297 ? apiKeySource 298 : hasApiKeyEnvVar 299 ? 'ANTHROPIC_API_KEY' 300 : null 301 const output: Record<string, string | boolean | null> = { 302 loggedIn, 303 authMethod, 304 apiProvider, 305 } 306 if (resolvedApiKeySource) { 307 output.apiKeySource = resolvedApiKeySource 308 } 309 if (authMethod === 'claude.ai') { 310 output.email = oauthAccount?.emailAddress ?? null 311 output.orgId = oauthAccount?.organizationUuid ?? null 312 output.orgName = oauthAccount?.organizationName ?? null 313 output.subscriptionType = subscriptionType ?? null 314 } 315 316 process.stdout.write(jsonStringify(output, null, 2) + '\n') 317 } 318 process.exit(loggedIn ? 0 : 1) 319} 320 321export async function authLogout(): Promise<void> { 322 try { 323 await performLogout({ clearOnboarding: false }) 324 } catch { 325 process.stderr.write('Failed to log out.\n') 326 process.exit(1) 327 } 328 process.stdout.write('Successfully logged out from your Anthropic account.\n') 329 process.exit(0) 330}