feat(api): add auth tokens REST endpoints

- GET /api/settings/auth/tokens - masked token status
- GET /api/settings/auth/tokens/raw - unmasked (Cache-Control: no-store)
- PUT /api/settings/auth/tokens - update API key or management secret
- POST /api/settings/auth/tokens/regenerate-secret - generate secure secret
- POST /api/settings/auth/tokens/reset - reset to defaults
This commit is contained in:
kaitranntt
2025-12-25 14:36:34 -05:00
parent 0c6491c9d2
commit ffd499698e
+147
View File
@@ -8,6 +8,15 @@ import * as path from 'path';
import { getCcsDir, loadSettings } from '../../utils/config-manager';
import { isSensitiveKey, maskSensitiveValue } from '../../utils/sensitive-keys';
import { listVariants } from '../../cliproxy/services/variant-service';
import {
generateSecureToken,
maskToken,
getAuthSummary,
setGlobalApiKey,
setGlobalManagementSecret,
resetAuthToDefaults,
} from '../../cliproxy';
import { regenerateConfig } from '../../cliproxy/config-generator';
import type { Settings } from '../../types/config';
const router = Router();
@@ -258,4 +267,142 @@ router.delete('/:profile/presets/:name', (req: Request, res: Response): void =>
}
});
// ==================== Auth Tokens ====================
/**
* GET /api/settings/auth/tokens - Get current auth token status (masked)
*/
router.get('/auth/tokens', (_req: Request, res: Response): void => {
try {
const summary = getAuthSummary();
res.json({
apiKey: {
value: maskToken(summary.apiKey.value),
isCustom: summary.apiKey.isCustom,
},
managementSecret: {
value: maskToken(summary.managementSecret.value),
isCustom: summary.managementSecret.isCustom,
},
});
} catch (error) {
res.status(500).json({ error: (error as Error).message });
}
});
/**
* GET /api/settings/auth/tokens/raw - Get current auth tokens unmasked
* NOTE: Sensitive endpoint - no caching, localhost only
*/
router.get('/auth/tokens/raw', (_req: Request, res: Response): void => {
try {
// Prevent caching of sensitive data
res.setHeader('Cache-Control', 'no-store');
const summary = getAuthSummary();
res.json({
apiKey: {
value: summary.apiKey.value,
isCustom: summary.apiKey.isCustom,
},
managementSecret: {
value: summary.managementSecret.value,
isCustom: summary.managementSecret.isCustom,
},
});
} catch (error) {
res.status(500).json({ error: (error as Error).message });
}
});
/**
* PUT /api/settings/auth/tokens - Update auth tokens
*/
router.put('/auth/tokens', (req: Request, res: Response): void => {
try {
const { apiKey, managementSecret } = req.body;
if (apiKey !== undefined) {
setGlobalApiKey(apiKey || undefined);
}
if (managementSecret !== undefined) {
setGlobalManagementSecret(managementSecret || undefined);
}
// Regenerate CLIProxy config to apply changes
regenerateConfig();
const summary = getAuthSummary();
res.json({
success: true,
apiKey: {
value: maskToken(summary.apiKey.value),
isCustom: summary.apiKey.isCustom,
},
managementSecret: {
value: maskToken(summary.managementSecret.value),
isCustom: summary.managementSecret.isCustom,
},
message: 'Restart CLIProxy to apply changes',
});
} catch (error) {
res.status(500).json({ error: (error as Error).message });
}
});
/**
* POST /api/settings/auth/tokens/regenerate-secret - Generate new management secret
*/
router.post('/auth/tokens/regenerate-secret', (_req: Request, res: Response): void => {
try {
const newSecret = generateSecureToken(32);
setGlobalManagementSecret(newSecret);
// Regenerate CLIProxy config to apply changes
regenerateConfig();
res.json({
success: true,
managementSecret: {
value: maskToken(newSecret),
isCustom: true,
},
message: 'Restart CLIProxy to apply changes',
});
} catch (error) {
res.status(500).json({ error: (error as Error).message });
}
});
/**
* POST /api/settings/auth/tokens/reset - Reset auth tokens to defaults
*/
router.post('/auth/tokens/reset', (_req: Request, res: Response): void => {
try {
resetAuthToDefaults();
// Regenerate CLIProxy config to apply changes
regenerateConfig();
const summary = getAuthSummary();
res.json({
success: true,
apiKey: {
value: maskToken(summary.apiKey.value),
isCustom: summary.apiKey.isCustom,
},
managementSecret: {
value: maskToken(summary.managementSecret.value),
isCustom: summary.managementSecret.isCustom,
},
message: 'Tokens reset to defaults. Restart CLIProxy to apply.',
});
} catch (error) {
res.status(500).json({ error: (error as Error).message });
}
});
export default router;