mirror of
https://github.com/tiennm99/ccs.git
synced 2026-07-16 06:16:37 +00:00
feat(cliproxy): add customizable auth token manager
- 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
This commit is contained in:
@@ -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,
|
||||
},
|
||||
};
|
||||
}
|
||||
@@ -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';
|
||||
|
||||
@@ -131,6 +131,8 @@ function mergeWithDefaults(partial: Partial<UnifiedConfig>): 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,
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
Reference in New Issue
Block a user