Files
ccs/src/commands/config-auth/setup-command.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

134 lines
4.2 KiB
TypeScript

/**
* Config Auth Setup Command
*
* Interactive wizard for configuring dashboard authentication.
* Prompts for username and password, hashes password with bcrypt,
* and saves to config.yaml.
*/
import bcrypt from 'bcrypt';
import { InteractivePrompt } from '../../utils/prompt';
import { initUI, header, subheader, ok, fail, info, warn, dim } from '../../utils/ui';
import type { AuthSetupResult } from './types';
import { mutateConfig } from '../../config/config-loader-facade';
const BCRYPT_ROUNDS = 10;
const MIN_PASSWORD_LENGTH = 8;
/**
* Validate username (non-empty, alphanumeric with underscores)
*/
function validateUsername(value: string): string | null {
if (!value || value.trim().length === 0) {
return 'Username cannot be empty';
}
if (!/^[a-zA-Z][a-zA-Z0-9_-]*$/.test(value)) {
return 'Username must start with letter, contain only letters/numbers/underscores/hyphens';
}
if (value.length < 3) {
return 'Username must be at least 3 characters';
}
return null;
}
/**
* Handle setup command - interactive wizard
*/
export async function handleSetup(): Promise<AuthSetupResult> {
await initUI();
console.log('');
console.log(header('Dashboard Auth Setup'));
console.log('');
console.log(info('Configure username and password for dashboard access.'));
console.log(dim(' Password will be hashed with bcrypt before storage.'));
console.log('');
// Check for ENV overrides
if (
process.env.CCS_DASHBOARD_AUTH_ENABLED ||
process.env.CCS_DASHBOARD_USERNAME ||
process.env.CCS_DASHBOARD_PASSWORD_HASH
) {
console.log(warn('Environment variables detected - they will override config values:'));
if (process.env.CCS_DASHBOARD_AUTH_ENABLED) {
console.log(` CCS_DASHBOARD_AUTH_ENABLED=${process.env.CCS_DASHBOARD_AUTH_ENABLED}`);
}
if (process.env.CCS_DASHBOARD_USERNAME) {
console.log(` CCS_DASHBOARD_USERNAME=${process.env.CCS_DASHBOARD_USERNAME}`);
}
if (process.env.CCS_DASHBOARD_PASSWORD_HASH) {
console.log(' CCS_DASHBOARD_PASSWORD_HASH=***');
}
console.log('');
}
try {
// Prompt for username
console.log(subheader('Username'));
const username = await InteractivePrompt.input('Enter username', {
validate: validateUsername,
});
console.log('');
// Prompt for password
console.log(subheader('Password'));
console.log(dim(` Minimum ${MIN_PASSWORD_LENGTH} characters`));
const password = await InteractivePrompt.password('Enter password');
// Validate password length
if (password.length < MIN_PASSWORD_LENGTH) {
console.log('');
console.log(fail(`Password must be at least ${MIN_PASSWORD_LENGTH} characters`));
return { success: false, error: 'Password too short' };
}
// Confirm password
const confirmPassword = await InteractivePrompt.password('Confirm password');
if (password !== confirmPassword) {
console.log('');
console.log(fail('Passwords do not match'));
return { success: false, error: 'Password mismatch' };
}
console.log('');
console.log(info('Hashing password...'));
// Hash password
const passwordHash = await bcrypt.hash(password, BCRYPT_ROUNDS);
const config = mutateConfig((currentConfig) => {
currentConfig.dashboard_auth = {
enabled: true,
username,
password_hash: passwordHash,
session_timeout_hours: currentConfig.dashboard_auth?.session_timeout_hours ?? 24,
};
});
console.log('');
console.log(ok('Dashboard authentication configured'));
console.log('');
console.log(info('Settings saved to ~/.ccs/config.yaml'));
console.log(info(`Username: ${username}`));
console.log(
info(`Session timeout: ${config.dashboard_auth?.session_timeout_hours ?? 24} hours`)
);
console.log('');
console.log(dim(' Start dashboard: ccs config'));
console.log(dim(' Show status: ccs config auth show'));
console.log(dim(' Disable auth: ccs config auth disable'));
console.log('');
return { success: true, username };
} catch (error) {
const err = error as Error;
console.log('');
console.log(fail(`Setup failed: ${err.message}`));
return { success: false, error: err.message };
}
}