Files
ccs/src/cliproxy/auth/auth-token-manager.ts
T
Tam Nhu Tran 4f6e61739c refactor(config): adopt config-loader-facade across the codebase
Issue #1161. Sweeps 127 files to import from
src/config/config-loader-facade.ts instead of unified-config-loader or
utils/config-manager directly.

WRITE callers (32 files): replaced raw saveUnifiedConfig /
mutateUnifiedConfig / updateUnifiedConfig calls with the facade's
cache-coherent wrappers saveConfig / mutateConfig / updateConfig. This
fixes a latent stale-cache window where direct writes through the
underlying loader bypassed the facade's memoization.

READ callers (95 files): mechanical import-path migration only —
function names unchanged because the facade re-exports them. No
behavior change.

Also updated:
- tests/unit/utils/browser/browser-setup.test.ts (DI interface rename)
- src/management/checks/image-analysis-check.ts (dynamic import rename)
- src/web-server/health-service.ts (dynamic require rename)
- src/ccs.ts (path prefix fix from sweep script)

After sweep: zero raw write callers remain outside src/config/. Direct
imports of config-manager remain only for symbols not in the facade
(getConfigPath, getCcsDirSource, etc). Behavior unchanged; full suite
passes 1824/1824.

Out of scope: switching loadOrCreateUnifiedConfig() callers to
getCachedConfig() — needs per-callsite cache-safety analysis. Tracked
as follow-up.

Refs #1161
2026-05-03 01:42:53 -04:00

195 lines
5.2 KiB
TypeScript

/**
* 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 { CCS_INTERNAL_API_KEY, CCS_CONTROL_PANEL_SECRET } from '../config/generator';
import { loadOrCreateUnifiedConfig, mutateConfig } from '../../config/config-loader-facade';
/**
* 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 {
mutateConfig((config) => {
if (!config.cliproxy.auth) {
config.cliproxy.auth = {};
}
if (apiKey === undefined) {
delete config.cliproxy.auth.api_key;
} else {
config.cliproxy.auth.api_key = apiKey;
}
});
}
/**
* 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 {
mutateConfig((config) => {
if (!config.cliproxy.auth) {
config.cliproxy.auth = {};
}
if (secret === undefined) {
delete config.cliproxy.auth.management_secret;
} else {
config.cliproxy.auth.management_secret = secret;
}
});
}
/**
* 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 {
mutateConfig((config) => {
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;
if (Object.keys(variant.auth).length === 0) {
delete variant.auth;
}
} else {
variant.auth.api_key = apiKey;
}
});
}
/**
* Reset all auth settings to defaults.
* Removes cliproxy.auth and all variant auth overrides.
*/
export function resetAuthToDefaults(): void {
mutateConfig((config) => {
delete config.cliproxy.auth;
for (const variantName of Object.keys(config.cliproxy.variants)) {
const variant = config.cliproxy.variants[variantName];
if (variant.auth) {
delete variant.auth;
}
}
});
}
/**
* 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,
},
};
}