mirror of
https://github.com/tiennm99/ccs.git
synced 2026-09-10 06:20:13 +00:00
refactor(api-key-validator): extract shared validation logic, remove unnecessary comments
- Extract common validation logic into validateProviderKey() function - Convert validateGlmKey() and validateMiniMaxKey() to thin wrappers - Remove 15+ unnecessary inline comments explaining obvious code - Remove verbose JSDoc that duplicates function signatures - Reduce file from 236 to 148 lines (37% reduction) 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude <noreply@anthropic.com>
This commit is contained in:
+122
-196
@@ -26,210 +26,136 @@ const DEFAULT_PLACEHOLDERS = [
|
|||||||
'',
|
'',
|
||||||
];
|
];
|
||||||
|
|
||||||
/**
|
interface ProviderConfig {
|
||||||
* Validate GLM API key with quick health check
|
name: string;
|
||||||
*
|
profile: string;
|
||||||
* @param apiKey - The ANTHROPIC_AUTH_TOKEN value
|
defaultBaseUrl: string;
|
||||||
* @param baseUrl - Optional base URL (defaults to Z.AI)
|
path: string;
|
||||||
* @param timeoutMs - Timeout in milliseconds (default 2000)
|
displayName: string;
|
||||||
*/
|
dashboardUrl: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
async function validateProviderKey(
|
||||||
|
apiKey: string,
|
||||||
|
config: ProviderConfig,
|
||||||
|
baseUrl?: string,
|
||||||
|
timeoutMs = 2000
|
||||||
|
): Promise<ValidationResult> {
|
||||||
|
if (process.env.CCS_SKIP_PREFLIGHT === '1') {
|
||||||
|
return { valid: true };
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!apiKey || DEFAULT_PLACEHOLDERS.includes(apiKey.toUpperCase())) {
|
||||||
|
return {
|
||||||
|
valid: false,
|
||||||
|
error: 'API key not configured',
|
||||||
|
suggestion:
|
||||||
|
`Set ANTHROPIC_AUTH_TOKEN in ~/.ccs/${config.profile}.settings.json\n` +
|
||||||
|
`Or run: ccs config -> API Profiles -> ${config.name}`,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
const targetBase = baseUrl || config.defaultBaseUrl;
|
||||||
|
let url: URL;
|
||||||
|
try {
|
||||||
|
url = new URL(config.path, targetBase);
|
||||||
|
} catch {
|
||||||
|
return { valid: true };
|
||||||
|
}
|
||||||
|
|
||||||
|
return new Promise((resolve) => {
|
||||||
|
const isHttps = url.protocol === 'https:';
|
||||||
|
const httpModule = isHttps ? https : http;
|
||||||
|
const defaultPort = isHttps ? 443 : 80;
|
||||||
|
|
||||||
|
const options: https.RequestOptions = {
|
||||||
|
hostname: url.hostname,
|
||||||
|
port: url.port || defaultPort,
|
||||||
|
path: url.pathname,
|
||||||
|
method: 'GET',
|
||||||
|
headers: {
|
||||||
|
Authorization: `Bearer ${apiKey}`,
|
||||||
|
'User-Agent': 'CCS-Preflight/1.0',
|
||||||
|
},
|
||||||
|
};
|
||||||
|
|
||||||
|
const req = httpModule.request(options, (res) => {
|
||||||
|
clearTimeout(timeoutId);
|
||||||
|
|
||||||
|
if (res.statusCode === 200) {
|
||||||
|
resolve({ valid: true });
|
||||||
|
} else if (res.statusCode === 401 || res.statusCode === 403) {
|
||||||
|
resolve({
|
||||||
|
valid: false,
|
||||||
|
error: `API key rejected by ${config.displayName}`,
|
||||||
|
suggestion:
|
||||||
|
`Your key may have expired. To fix:\n` +
|
||||||
|
` 1. Go to ${config.dashboardUrl} and regenerate your API key\n` +
|
||||||
|
` 2. Update ~/.ccs/${config.profile}.settings.json with new key\n` +
|
||||||
|
` 3. Or run: ccs config -> API Profiles -> ${config.name}`,
|
||||||
|
});
|
||||||
|
} else {
|
||||||
|
if (process.env.CCS_DEBUG === '1') {
|
||||||
|
console.error(
|
||||||
|
`[CCS-Preflight] Unexpected status ${res.statusCode} from ${url.href} - fail-open`
|
||||||
|
);
|
||||||
|
}
|
||||||
|
resolve({ valid: true });
|
||||||
|
}
|
||||||
|
|
||||||
|
res.resume();
|
||||||
|
});
|
||||||
|
|
||||||
|
req.on('error', () => {
|
||||||
|
clearTimeout(timeoutId);
|
||||||
|
resolve({ valid: true });
|
||||||
|
});
|
||||||
|
|
||||||
|
const timeoutId = setTimeout(() => {
|
||||||
|
req.destroy();
|
||||||
|
resolve({ valid: true });
|
||||||
|
}, timeoutMs);
|
||||||
|
|
||||||
|
req.end();
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
export async function validateGlmKey(
|
export async function validateGlmKey(
|
||||||
apiKey: string,
|
apiKey: string,
|
||||||
baseUrl?: string,
|
baseUrl?: string,
|
||||||
timeoutMs = 2000
|
timeoutMs?: number
|
||||||
): Promise<ValidationResult> {
|
): Promise<ValidationResult> {
|
||||||
// Skip if disabled
|
return validateProviderKey(
|
||||||
if (process.env.CCS_SKIP_PREFLIGHT === '1') {
|
apiKey,
|
||||||
return { valid: true };
|
{
|
||||||
}
|
name: 'GLM',
|
||||||
|
profile: 'glm',
|
||||||
// Basic format check - detect placeholders
|
defaultBaseUrl: 'https://api.z.ai',
|
||||||
if (!apiKey || DEFAULT_PLACEHOLDERS.includes(apiKey.toUpperCase())) {
|
path: '/api/anthropic/v1/models',
|
||||||
return {
|
displayName: 'Z.AI',
|
||||||
valid: false,
|
dashboardUrl: 'Z.AI dashboard',
|
||||||
error: 'API key not configured',
|
},
|
||||||
suggestion:
|
baseUrl,
|
||||||
'Set ANTHROPIC_AUTH_TOKEN in ~/.ccs/glm.settings.json\n' +
|
timeoutMs
|
||||||
'Or run: ccs config -> API Profiles -> GLM',
|
);
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
// Determine validation endpoint
|
|
||||||
// Z.AI uses /api/anthropic path, we can test with a minimal request
|
|
||||||
const targetBase = baseUrl || 'https://api.z.ai';
|
|
||||||
let url: URL;
|
|
||||||
try {
|
|
||||||
url = new URL('/api/anthropic/v1/models', targetBase);
|
|
||||||
} catch {
|
|
||||||
// Invalid URL - fail-open
|
|
||||||
return { valid: true };
|
|
||||||
}
|
|
||||||
|
|
||||||
return new Promise((resolve) => {
|
|
||||||
// Determine protocol - use http module for http:// URLs
|
|
||||||
const isHttps = url.protocol === 'https:';
|
|
||||||
const httpModule = isHttps ? https : http;
|
|
||||||
const defaultPort = isHttps ? 443 : 80;
|
|
||||||
|
|
||||||
const options: https.RequestOptions = {
|
|
||||||
hostname: url.hostname,
|
|
||||||
port: url.port || defaultPort,
|
|
||||||
path: url.pathname,
|
|
||||||
method: 'GET',
|
|
||||||
headers: {
|
|
||||||
Authorization: `Bearer ${apiKey}`,
|
|
||||||
'User-Agent': 'CCS-Preflight/1.0',
|
|
||||||
},
|
|
||||||
};
|
|
||||||
|
|
||||||
const req = httpModule.request(options, (res) => {
|
|
||||||
clearTimeout(timeoutId);
|
|
||||||
|
|
||||||
if (res.statusCode === 200) {
|
|
||||||
resolve({ valid: true });
|
|
||||||
} else if (res.statusCode === 401 || res.statusCode === 403) {
|
|
||||||
resolve({
|
|
||||||
valid: false,
|
|
||||||
error: 'API key rejected by Z.AI',
|
|
||||||
suggestion:
|
|
||||||
'Your key may have expired. To fix:\n' +
|
|
||||||
' 1. Go to Z.AI dashboard and regenerate your API key\n' +
|
|
||||||
' 2. Update ~/.ccs/glm.settings.json with the new key\n' +
|
|
||||||
' 3. Or run: ccs config -> API Profiles -> GLM',
|
|
||||||
});
|
|
||||||
} else {
|
|
||||||
// Other errors (404, 500, etc.) - fail-open, let Claude CLI handle
|
|
||||||
// Debug log for diagnostics when CCS_DEBUG is set
|
|
||||||
if (process.env.CCS_DEBUG === '1') {
|
|
||||||
console.error(
|
|
||||||
`[CCS-Preflight] Unexpected status ${res.statusCode} from ${url.href} - fail-open`
|
|
||||||
);
|
|
||||||
}
|
|
||||||
resolve({ valid: true });
|
|
||||||
}
|
|
||||||
|
|
||||||
// Consume response body to free resources
|
|
||||||
res.resume();
|
|
||||||
});
|
|
||||||
|
|
||||||
req.on('error', () => {
|
|
||||||
clearTimeout(timeoutId);
|
|
||||||
// Network error - fail-open
|
|
||||||
resolve({ valid: true });
|
|
||||||
});
|
|
||||||
|
|
||||||
// Set timeout after request is created so we can destroy it on timeout
|
|
||||||
const timeoutId = setTimeout(() => {
|
|
||||||
// Abort request to prevent TCP connection leak
|
|
||||||
req.destroy();
|
|
||||||
// Fail-open on timeout - let Claude CLI handle it
|
|
||||||
resolve({ valid: true });
|
|
||||||
}, timeoutMs);
|
|
||||||
|
|
||||||
req.end();
|
|
||||||
});
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
* Validate MiniMax API key with quick health check
|
|
||||||
*
|
|
||||||
* @param apiKey - The ANTHROPIC_AUTH_TOKEN value
|
|
||||||
* @param baseUrl - Optional base URL (defaults to MiniMax)
|
|
||||||
* @param timeoutMs - Timeout in milliseconds (default 2000)
|
|
||||||
*/
|
|
||||||
export async function validateMiniMaxKey(
|
export async function validateMiniMaxKey(
|
||||||
apiKey: string,
|
apiKey: string,
|
||||||
baseUrl?: string,
|
baseUrl?: string,
|
||||||
timeoutMs = 2000
|
timeoutMs?: number
|
||||||
): Promise<ValidationResult> {
|
): Promise<ValidationResult> {
|
||||||
// Skip if disabled
|
return validateProviderKey(
|
||||||
if (process.env.CCS_SKIP_PREFLIGHT === '1') {
|
apiKey,
|
||||||
return { valid: true };
|
{
|
||||||
}
|
name: 'MiniMax',
|
||||||
|
profile: 'mm',
|
||||||
// Basic format check - detect placeholders
|
defaultBaseUrl: 'https://api.minimax.io',
|
||||||
if (!apiKey || DEFAULT_PLACEHOLDERS.includes(apiKey.toUpperCase())) {
|
path: '/anthropic/v1/models',
|
||||||
return {
|
displayName: 'MiniMax',
|
||||||
valid: false,
|
dashboardUrl: 'platform.minimax.io',
|
||||||
error: 'API key not configured',
|
},
|
||||||
suggestion:
|
baseUrl,
|
||||||
'Set ANTHROPIC_AUTH_TOKEN in ~/.ccs/mm.settings.json\n' +
|
timeoutMs
|
||||||
'Or run: ccs config -> API Profiles -> MiniMax',
|
);
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
// Determine validation endpoint
|
|
||||||
// MiniMax uses /anthropic path, we can test with a minimal request
|
|
||||||
const targetBase = baseUrl || 'https://api.minimax.io';
|
|
||||||
let url: URL;
|
|
||||||
try {
|
|
||||||
url = new URL('/anthropic/v1/models', targetBase);
|
|
||||||
} catch {
|
|
||||||
// Invalid URL - fail-open
|
|
||||||
return { valid: true };
|
|
||||||
}
|
|
||||||
|
|
||||||
return new Promise((resolve) => {
|
|
||||||
// Determine protocol - use http module for http:// URLs
|
|
||||||
const isHttps = url.protocol === 'https:';
|
|
||||||
const httpModule = isHttps ? https : http;
|
|
||||||
const defaultPort = isHttps ? 443 : 80;
|
|
||||||
|
|
||||||
const options: https.RequestOptions = {
|
|
||||||
hostname: url.hostname,
|
|
||||||
port: url.port || defaultPort,
|
|
||||||
path: url.pathname,
|
|
||||||
method: 'GET',
|
|
||||||
headers: {
|
|
||||||
Authorization: `Bearer ${apiKey}`,
|
|
||||||
'User-Agent': 'CCS-Preflight/1.0',
|
|
||||||
},
|
|
||||||
};
|
|
||||||
|
|
||||||
const req = httpModule.request(options, (res) => {
|
|
||||||
clearTimeout(timeoutId);
|
|
||||||
|
|
||||||
if (res.statusCode === 200) {
|
|
||||||
resolve({ valid: true });
|
|
||||||
} else if (res.statusCode === 401 || res.statusCode === 403) {
|
|
||||||
resolve({
|
|
||||||
valid: false,
|
|
||||||
error: 'API key rejected by MiniMax',
|
|
||||||
suggestion:
|
|
||||||
'Your key may have expired. To fix:\n' +
|
|
||||||
' 1. Go to platform.minimax.io and regenerate your API key\n' +
|
|
||||||
' 2. Update ~/.ccs/mm.settings.json with new key\n' +
|
|
||||||
' 3. Or run: ccs config -> API Profiles -> MiniMax',
|
|
||||||
});
|
|
||||||
} else {
|
|
||||||
// Other errors (404, 500, etc.) - fail-open, let Claude CLI handle
|
|
||||||
// Debug log for diagnostics when CCS_DEBUG is set
|
|
||||||
if (process.env.CCS_DEBUG === '1') {
|
|
||||||
console.error(
|
|
||||||
`[CCS-Preflight] Unexpected status ${res.statusCode} from ${url.href} - fail-open`
|
|
||||||
);
|
|
||||||
}
|
|
||||||
resolve({ valid: true });
|
|
||||||
}
|
|
||||||
|
|
||||||
// Consume response body to free resources
|
|
||||||
res.resume();
|
|
||||||
});
|
|
||||||
|
|
||||||
req.on('error', () => {
|
|
||||||
clearTimeout(timeoutId);
|
|
||||||
// Network error - fail-open
|
|
||||||
resolve({ valid: true });
|
|
||||||
});
|
|
||||||
|
|
||||||
// Set timeout after request is created so we can destroy it on timeout
|
|
||||||
const timeoutId = setTimeout(() => {
|
|
||||||
// Abort request to prevent TCP connection leak
|
|
||||||
req.destroy();
|
|
||||||
// Fail-open on timeout - let Claude CLI handle it
|
|
||||||
resolve({ valid: true });
|
|
||||||
}, timeoutMs);
|
|
||||||
|
|
||||||
req.end();
|
|
||||||
});
|
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user