mirror of
https://github.com/tiennm99/ccs.git
synced 2026-09-02 16:19:27 +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';
|
||||
|
||||
Reference in New Issue
Block a user