mirror of
https://github.com/siteboon/claudecodeui.git
synced 2026-05-26 00:04:02 +00:00
Rename provider auth/MCP contracts to remove the overloaded Runtime suffix so the shared interfaces read as stable provider capabilities instead of execution implementation details. Add a consistent provider-first auth class naming convention by renaming ClaudeAuthProvider, CodexAuthProvider, CursorAuthProvider, and GeminiAuthProvider to ClaudeProviderAuth, CodexProviderAuth, CursorProviderAuth, and GeminiProviderAuth. This keeps the provider module API easier to scan and aligns auth naming with the main provider ownership model.
127 lines
4.0 KiB
TypeScript
127 lines
4.0 KiB
TypeScript
import { readFile } from 'node:fs/promises';
|
|
import os from 'node:os';
|
|
import path from 'node:path';
|
|
|
|
import spawn from 'cross-spawn';
|
|
|
|
import type { IProviderAuth } from '@/shared/interfaces.js';
|
|
import type { ProviderAuthStatus } from '@/shared/types.js';
|
|
import { readObjectRecord, readOptionalString } from '@/shared/utils.js';
|
|
|
|
type ClaudeCredentialsStatus = {
|
|
authenticated: boolean;
|
|
email: string | null;
|
|
method: string | null;
|
|
error?: string;
|
|
};
|
|
|
|
export class ClaudeProviderAuth implements IProviderAuth {
|
|
/**
|
|
* Checks whether the Claude Code CLI is available on this host.
|
|
*/
|
|
private checkInstalled(): boolean {
|
|
const cliPath = process.env.CLAUDE_CLI_PATH || 'claude';
|
|
try {
|
|
spawn.sync(cliPath, ['--version'], { stdio: 'ignore', timeout: 5000 });
|
|
return true;
|
|
} catch {
|
|
return false;
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Returns Claude installation and credential status using Claude Code's auth priority.
|
|
*/
|
|
async getStatus(): Promise<ProviderAuthStatus> {
|
|
console.log("Checking Claude authentication status...")
|
|
const installed = this.checkInstalled();
|
|
|
|
if (!installed) {
|
|
return {
|
|
installed,
|
|
provider: 'claude',
|
|
authenticated: false,
|
|
email: null,
|
|
method: null,
|
|
error: 'Claude Code CLI is not installed',
|
|
};
|
|
}
|
|
|
|
const credentials = await this.checkCredentials();
|
|
|
|
console.log("Credientials status for Claude:", credentials)
|
|
return {
|
|
installed,
|
|
provider: 'claude',
|
|
authenticated: credentials.authenticated,
|
|
email: credentials.authenticated ? credentials.email || 'Authenticated' : credentials.email,
|
|
method: credentials.method,
|
|
error: credentials.authenticated ? undefined : credentials.error || 'Not authenticated',
|
|
};
|
|
}
|
|
|
|
/**
|
|
* Reads Claude settings env values that the CLI can use even when the server process env is empty.
|
|
*/
|
|
private async loadSettingsEnv(): Promise<Record<string, unknown>> {
|
|
try {
|
|
const settingsPath = path.join(os.homedir(), '.claude', 'settings.json');
|
|
const content = await readFile(settingsPath, 'utf8');
|
|
const settings = readObjectRecord(JSON.parse(content));
|
|
console.log("Settings env for Claude:", settings)
|
|
return readObjectRecord(settings?.env) ?? {};
|
|
} catch {
|
|
return {};
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Checks Claude credentials in the same priority order used by Claude Code.
|
|
*/
|
|
private async checkCredentials(): Promise<ClaudeCredentialsStatus> {
|
|
if (process.env.ANTHROPIC_API_KEY?.trim()) {
|
|
return { authenticated: true, email: 'API Key Auth', method: 'api_key' };
|
|
}
|
|
|
|
const settingsEnv = await this.loadSettingsEnv();
|
|
if (readOptionalString(settingsEnv.ANTHROPIC_API_KEY)) {
|
|
return { authenticated: true, email: 'API Key Auth', method: 'api_key' };
|
|
}
|
|
|
|
if (readOptionalString(settingsEnv.ANTHROPIC_AUTH_TOKEN)) {
|
|
return { authenticated: true, email: 'Configured via settings.json', method: 'api_key' };
|
|
}
|
|
|
|
try {
|
|
const credPath = path.join(os.homedir(), '.claude', '.credentials.json');
|
|
const content = await readFile(credPath, 'utf8');
|
|
const creds = readObjectRecord(JSON.parse(content)) ?? {};
|
|
const oauth = readObjectRecord(creds.claudeAiOauth);
|
|
const accessToken = readOptionalString(oauth?.accessToken);
|
|
|
|
if (accessToken) {
|
|
const expiresAt = typeof oauth?.expiresAt === 'number' ? oauth.expiresAt : undefined;
|
|
const email = readOptionalString(creds.email) ?? readOptionalString(creds.user) ?? null;
|
|
if (!expiresAt || Date.now() < expiresAt) {
|
|
return {
|
|
authenticated: true,
|
|
email,
|
|
method: 'credentials_file',
|
|
};
|
|
}
|
|
|
|
return {
|
|
authenticated: false,
|
|
email,
|
|
method: 'credentials_file',
|
|
error: 'OAuth token has expired. Please re-authenticate with claude login',
|
|
};
|
|
}
|
|
|
|
return { authenticated: false, email: null, method: null };
|
|
} catch {
|
|
return { authenticated: false, email: null, method: null };
|
|
}
|
|
}
|
|
}
|