mirror of
https://github.com/tiennm99/ccs.git
synced 2026-07-16 00:22:34 +00:00
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:
@@ -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;
|
||||
|
||||
Reference in New Issue
Block a user