From c4f09168ff35e52c8613a3181a86e4e4e5392dfc Mon Sep 17 00:00:00 2001 From: kaitranntt Date: Thu, 25 Dec 2025 14:35:43 -0500 Subject: [PATCH] feat(cliproxy): add customizable auth token manager MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - Add CLIProxyAuthConfig type with api_key and management_secret fields - Implement auth-token-manager.ts with inheritance chain: variant auth → global auth → default constants - Add secure token generation using crypto.randomBytes (256-bit entropy) - Export auth functions from cliproxy barrel (index.ts) - Bump UNIFIED_CONFIG_VERSION to 6 --- src/cliproxy/auth-token-manager.ts | 203 ++++++++++++++++++++++++++++ src/cliproxy/index.ts | 13 ++ src/config/unified-config-loader.ts | 2 + src/config/unified-config-types.ts | 18 ++- 4 files changed, 235 insertions(+), 1 deletion(-) create mode 100644 src/cliproxy/auth-token-manager.ts diff --git a/src/cliproxy/auth-token-manager.ts b/src/cliproxy/auth-token-manager.ts new file mode 100644 index 00000000..4daebb13 --- /dev/null +++ b/src/cliproxy/auth-token-manager.ts @@ -0,0 +1,203 @@ +/** + * Auth Token Manager for CLIProxyAPI + * + * Manages API key and management secret resolution with inheritance: + * - Per-variant override → Global config → Default constants + * + * Provides secure token generation for user customization. + */ + +import { randomBytes } from 'crypto'; +import { loadOrCreateUnifiedConfig, saveUnifiedConfig } from '../config/unified-config-loader'; +import { CCS_INTERNAL_API_KEY, CCS_CONTROL_PANEL_SECRET } from './config-generator'; + +/** + * Generate a cryptographically secure token. + * Uses CSPRNG (crypto.randomBytes) for proper entropy. + * + * @param length - Number of random bytes (default: 32 = 256-bit entropy) + * @returns Base64URL-encoded token (43 chars for 32 bytes) + */ +export function generateSecureToken(length = 32): string { + return randomBytes(length).toString('base64url'); +} + +/** + * Mask a token for display. + * Shows first 4 and last 4 chars: "ccs_...4f2a" + * Used by CLI commands and API routes. + */ +export function maskToken(token: string): string { + if (token.length <= 8) return '****'; + return `${token.slice(0, 4)}...${token.slice(-4)}`; +} + +/** + * Get effective API key with inheritance chain. + * Priority: variant auth → global cliproxy.auth → default constant + * + * @param variantName - Optional variant name for per-variant override + * @returns Resolved API key + */ +export function getEffectiveApiKey(variantName?: string): string { + const config = loadOrCreateUnifiedConfig(); + + // Priority 1: Per-variant override + if (variantName) { + const variant = config.cliproxy.variants[variantName]; + if (variant?.auth?.api_key) { + return variant.auth.api_key; + } + } + + // Priority 2: Global cliproxy.auth + if (config.cliproxy.auth?.api_key) { + return config.cliproxy.auth.api_key; + } + + // Priority 3: Default constant (backwards compatible) + return CCS_INTERNAL_API_KEY; +} + +/** + * Get effective management secret. + * Priority: global cliproxy.auth → default constant + * + * Note: Management secret is global-only (no per-variant override) + * as it controls the Control Panel access for the entire CLIProxy instance. + * + * @returns Resolved management secret + */ +export function getEffectiveManagementSecret(): string { + const config = loadOrCreateUnifiedConfig(); + + // Priority 1: Global cliproxy.auth + if (config.cliproxy.auth?.management_secret) { + return config.cliproxy.auth.management_secret; + } + + // Priority 2: Default constant (backwards compatible) + return CCS_CONTROL_PANEL_SECRET; +} + +/** + * Set global API key. + * Updates cliproxy.auth.api_key in config.yaml. + * + * @param apiKey - New API key (or undefined to reset to default) + */ +export function setGlobalApiKey(apiKey: string | undefined): void { + const config = loadOrCreateUnifiedConfig(); + + if (!config.cliproxy.auth) { + config.cliproxy.auth = {}; + } + + if (apiKey === undefined) { + delete config.cliproxy.auth.api_key; + } else { + config.cliproxy.auth.api_key = apiKey; + } + + saveUnifiedConfig(config); +} + +/** + * Set global management secret. + * Updates cliproxy.auth.management_secret in config.yaml. + * + * @param secret - New management secret (or undefined to reset to default) + */ +export function setGlobalManagementSecret(secret: string | undefined): void { + const config = loadOrCreateUnifiedConfig(); + + if (!config.cliproxy.auth) { + config.cliproxy.auth = {}; + } + + if (secret === undefined) { + delete config.cliproxy.auth.management_secret; + } else { + config.cliproxy.auth.management_secret = secret; + } + + saveUnifiedConfig(config); +} + +/** + * Set per-variant API key override. + * Updates variants[variantName].auth.api_key in config.yaml. + * + * @param variantName - Variant name + * @param apiKey - New API key (or undefined to remove override) + */ +export function setVariantApiKey(variantName: string, apiKey: string | undefined): void { + const config = loadOrCreateUnifiedConfig(); + const variant = config.cliproxy.variants[variantName]; + + if (!variant) { + throw new Error(`Variant '${variantName}' not found`); + } + + if (!variant.auth) { + variant.auth = {}; + } + + if (apiKey === undefined) { + delete variant.auth.api_key; + // Clean up empty auth object + if (Object.keys(variant.auth).length === 0) { + delete variant.auth; + } + } else { + variant.auth.api_key = apiKey; + } + + saveUnifiedConfig(config); +} + +/** + * Reset all auth settings to defaults. + * Removes cliproxy.auth and all variant auth overrides. + */ +export function resetAuthToDefaults(): void { + const config = loadOrCreateUnifiedConfig(); + + // Remove global auth + delete config.cliproxy.auth; + + // Remove all variant auth overrides + for (const variantName of Object.keys(config.cliproxy.variants)) { + const variant = config.cliproxy.variants[variantName]; + if (variant.auth) { + delete variant.auth; + } + } + + saveUnifiedConfig(config); +} + +/** + * Get auth configuration summary for display. + * Returns current effective values and whether they're customized. + */ +export function getAuthSummary(): { + apiKey: { value: string; isCustom: boolean }; + managementSecret: { value: string; isCustom: boolean }; +} { + const config = loadOrCreateUnifiedConfig(); + + const customApiKey = config.cliproxy.auth?.api_key; + const customSecret = config.cliproxy.auth?.management_secret; + + return { + apiKey: { + value: customApiKey || CCS_INTERNAL_API_KEY, + isCustom: !!customApiKey, + }, + managementSecret: { + value: customSecret || CCS_CONTROL_PANEL_SECRET, + isCustom: !!customSecret, + }, + }; +} diff --git a/src/cliproxy/index.ts b/src/cliproxy/index.ts index 6381dc93..edbf9fd0 100644 --- a/src/cliproxy/index.ts +++ b/src/cliproxy/index.ts @@ -135,3 +135,16 @@ export { detectRunningProxy, waitForProxyHealthy, reclaimOrphanedProxy } from '. // Startup lock (prevents race conditions between CCS processes) export type { LockResult } from './startup-lock'; export { acquireStartupLock, withStartupLock } from './startup-lock'; + +// Auth token manager (customizable API key and management secret) +export { + generateSecureToken, + maskToken, + getEffectiveApiKey, + getEffectiveManagementSecret, + setGlobalApiKey, + setGlobalManagementSecret, + setVariantApiKey, + resetAuthToDefaults, + getAuthSummary, +} from './auth-token-manager'; diff --git a/src/config/unified-config-loader.ts b/src/config/unified-config-loader.ts index 3817eedb..47a26602 100644 --- a/src/config/unified-config-loader.ts +++ b/src/config/unified-config-loader.ts @@ -131,6 +131,8 @@ function mergeWithDefaults(partial: Partial): UnifiedConfig { request_log: partial.cliproxy?.logging?.request_log ?? defaults.cliproxy.logging?.request_log ?? false, }, + // Auth config - preserve user values, no defaults (uses constants as fallback) + auth: partial.cliproxy?.auth, }, preferences: { ...defaults.preferences, diff --git a/src/config/unified-config-types.ts b/src/config/unified-config-types.ts index 710dc405..b2d7fc6f 100644 --- a/src/config/unified-config-types.ts +++ b/src/config/unified-config-types.ts @@ -15,8 +15,9 @@ * Version 3 = WebSearch config with model configuration for Gemini/OpenCode * Version 4 = Copilot API integration (GitHub Copilot proxy) * Version 5 = Remote proxy configuration (connect to remote CLIProxyAPI) + * Version 6 = Customizable auth tokens (API key and management secret) */ -export const UNIFIED_CONFIG_VERSION = 5; +export const UNIFIED_CONFIG_VERSION = 6; /** * Account configuration (formerly in profiles.json). @@ -65,6 +66,19 @@ export interface CLIProxyVariantConfig { settings?: string; /** Unique port for variant isolation (8318-8417) */ port?: number; + /** Per-variant auth override (optional) */ + auth?: CLIProxyAuthConfig; +} + +/** + * CLIProxy authentication configuration. + * Allows customization of API key and management secret for CLIProxyAPI. + */ +export interface CLIProxyAuthConfig { + /** API key for CCS-managed requests (default: 'ccs-internal-managed') */ + api_key?: string; + /** Management secret for Control Panel login (default: 'ccs') */ + management_secret?: string; } /** @@ -93,6 +107,8 @@ export interface CLIProxyConfig { logging?: CLIProxyLoggingConfig; /** Kiro: disable incognito browser mode (use normal browser to save credentials) */ kiro_no_incognito?: boolean; + /** Global auth configuration for CLIProxyAPI */ + auth?: CLIProxyAuthConfig; } /**