mirror of
https://github.com/tiennm99/ccs.git
synced 2026-07-16 06:16:37 +00:00
fix(channels): avoid secrets in --set-token argv (#1389)
This commit is contained in:
@@ -809,7 +809,7 @@ export function getOfficialChannelsLegacyEnableHelp(): string {
|
||||
}
|
||||
|
||||
export function getOfficialChannelTokenHelp(): string {
|
||||
return 'Use --set-token <channel>=<token>. If no channel is provided, Discord is assumed for backward compatibility.';
|
||||
return 'Use --set-token <channel> and pass the token via that channel env var (for example TELEGRAM_BOT_TOKEN=... ccs config channels --set-token telegram).';
|
||||
}
|
||||
|
||||
export function getOfficialChannelClearTokenHelp(): string {
|
||||
|
||||
@@ -54,31 +54,13 @@ interface ChannelsCommandOptions {
|
||||
setSelectionMissing: boolean;
|
||||
clearTokenAll: boolean;
|
||||
clearTokenChannel?: OfficialChannelId;
|
||||
setToken?: { channelId: OfficialChannelId; token: string };
|
||||
setTokenChannel?: OfficialChannelId;
|
||||
setTokenMissing: boolean;
|
||||
clearTokenInvalid?: string;
|
||||
setTokenInvalid?: string;
|
||||
help: boolean;
|
||||
}
|
||||
|
||||
function parseTokenAssignment(value: string): {
|
||||
channelId: OfficialChannelId;
|
||||
token: string;
|
||||
} | null {
|
||||
const separatorIndex = value.indexOf('=');
|
||||
if (separatorIndex === -1) {
|
||||
return value.trim() ? { channelId: 'discord', token: value.trim() } : null;
|
||||
}
|
||||
|
||||
const channelId = value.slice(0, separatorIndex).trim().toLowerCase();
|
||||
const token = value.slice(separatorIndex + 1).trim();
|
||||
if (!isOfficialChannelId(channelId) || !token) {
|
||||
return null;
|
||||
}
|
||||
|
||||
return { channelId, token };
|
||||
}
|
||||
|
||||
export function parseChannelsCommandArgs(args: string[]): ChannelsCommandOptions {
|
||||
const setSelection = extractOption(args, ['--set']);
|
||||
const setToken = extractOption(args, ['--set-token']);
|
||||
@@ -100,11 +82,13 @@ export function parseChannelsCommandArgs(args: string[]): ChannelsCommandOptions
|
||||
}
|
||||
}
|
||||
|
||||
let parsedSetToken: { channelId: OfficialChannelId; token: string } | undefined;
|
||||
let parsedSetTokenChannel: OfficialChannelId | undefined;
|
||||
let setTokenInvalid: string | undefined;
|
||||
if (setToken.found && !setToken.missingValue && setToken.value) {
|
||||
parsedSetToken = parseTokenAssignment(setToken.value) ?? undefined;
|
||||
if (!parsedSetToken) {
|
||||
const channelId = setToken.value.trim().toLowerCase();
|
||||
if (isOfficialChannelId(channelId)) {
|
||||
parsedSetTokenChannel = channelId;
|
||||
} else {
|
||||
setTokenInvalid = setToken.value;
|
||||
}
|
||||
}
|
||||
@@ -120,7 +104,7 @@ export function parseChannelsCommandArgs(args: string[]): ChannelsCommandOptions
|
||||
clearTokenAll,
|
||||
clearTokenChannel,
|
||||
clearTokenInvalid,
|
||||
setToken: parsedSetToken,
|
||||
setTokenChannel: parsedSetTokenChannel,
|
||||
setTokenMissing: setToken.found && setToken.missingValue,
|
||||
setTokenInvalid,
|
||||
help: hasAnyFlag(args, ['--help', '-h']),
|
||||
@@ -153,7 +137,7 @@ function showHelp(): void {
|
||||
` ${color('--unattended', 'command')} Also add --dangerously-skip-permissions`
|
||||
);
|
||||
console.log(` ${color('--no-unattended', 'command')} Disable unattended runtime flag`);
|
||||
console.log(` ${color('--set-token <spec>', 'command')} ${getOfficialChannelTokenHelp()}`);
|
||||
console.log(` ${color('--set-token <channel>', 'command')} ${getOfficialChannelTokenHelp()}`);
|
||||
console.log(
|
||||
` ${color('--clear-token [channel]', 'command')} ${getOfficialChannelClearTokenHelp()}`
|
||||
);
|
||||
@@ -173,7 +157,7 @@ function showHelp(): void {
|
||||
` $ ${color('ccs config channels --set all', 'command')} ${dim('# Enable all official channels')}`
|
||||
);
|
||||
console.log(
|
||||
` $ ${color('ccs config channels --set-token telegram=123:abc', 'command')} ${dim('# Save TELEGRAM_BOT_TOKEN')}`
|
||||
` $ ${color('TELEGRAM_BOT_TOKEN=123:abc ccs config channels --set-token telegram', 'command')} ${dim('# Save TELEGRAM_BOT_TOKEN')}`
|
||||
);
|
||||
console.log(
|
||||
` $ ${color('ccs config channels --clear-token discord', 'command')} ${dim('# Clear one token')}`
|
||||
@@ -395,7 +379,9 @@ export async function handleConfigChannelsCommand(args: string[]): Promise<void>
|
||||
}
|
||||
if (options.setTokenInvalid) {
|
||||
console.error(
|
||||
fail(`Invalid --set-token value: ${options.setTokenInvalid} (use <channel>=<token>)`)
|
||||
fail(
|
||||
`Invalid --set-token value: ${options.setTokenInvalid} (use ${getOfficialChannelChoices()})`
|
||||
)
|
||||
);
|
||||
process.exitCode = 1;
|
||||
return;
|
||||
@@ -444,12 +430,19 @@ export async function handleConfigChannelsCommand(args: string[]): Promise<void>
|
||||
updateConfig({ channels: nextConfig });
|
||||
}
|
||||
|
||||
if (options.setToken) {
|
||||
if (!getOfficialChannelTokenIds().includes(options.setToken.channelId)) {
|
||||
throw new Error(`${options.setToken.channelId} does not use a bot token.`);
|
||||
if (options.setTokenChannel) {
|
||||
if (!getOfficialChannelTokenIds().includes(options.setTokenChannel)) {
|
||||
throw new Error(`${options.setTokenChannel} does not use a bot token.`);
|
||||
}
|
||||
setConfiguredOfficialChannelToken(options.setToken.channelId, options.setToken.token);
|
||||
console.log(ok(`${getOfficialChannelDisplayName(options.setToken.channelId)} token saved`));
|
||||
const envKey = getOfficialChannelEnvKey(options.setTokenChannel);
|
||||
const token = envKey ? process.env[envKey]?.trim() : '';
|
||||
if (!token) {
|
||||
throw new Error(
|
||||
`${getOfficialChannelDisplayName(options.setTokenChannel)} token missing. Set ${envKey} in your environment and rerun.`
|
||||
);
|
||||
}
|
||||
setConfiguredOfficialChannelToken(options.setTokenChannel, token);
|
||||
console.log(ok(`${getOfficialChannelDisplayName(options.setTokenChannel)} token saved`));
|
||||
console.log('');
|
||||
}
|
||||
|
||||
|
||||
@@ -2,35 +2,32 @@ import { describe, expect, it } from 'bun:test';
|
||||
import { parseChannelsCommandArgs } from '../../../src/commands/config-channels-command';
|
||||
|
||||
describe('config channels command parser', () => {
|
||||
it('parses selection, unattended mode, and token input', () => {
|
||||
it('parses selection, unattended mode, and token channel input', () => {
|
||||
const result = parseChannelsCommandArgs([
|
||||
'--set',
|
||||
'telegram,discord',
|
||||
'--unattended',
|
||||
'--set-token',
|
||||
'telegram=telegram-secret',
|
||||
'telegram',
|
||||
]);
|
||||
|
||||
expect(result.setSelection).toBe('telegram,discord');
|
||||
expect(result.unattended).toBe(true);
|
||||
expect(result.setToken).toEqual({
|
||||
channelId: 'telegram',
|
||||
token: 'telegram-secret',
|
||||
});
|
||||
expect(result.setTokenChannel).toBe('telegram');
|
||||
});
|
||||
|
||||
it('supports inline token assignment, legacy flags, and clear-token variants', () => {
|
||||
it('supports legacy flags and clear-token variants', () => {
|
||||
const result = parseChannelsCommandArgs([
|
||||
'--disable',
|
||||
'--no-unattended',
|
||||
'--set-token=abc',
|
||||
'--set-token=discord',
|
||||
]);
|
||||
const clearAll = parseChannelsCommandArgs(['--clear-token']);
|
||||
const clearOne = parseChannelsCommandArgs(['--clear-token', 'discord']);
|
||||
|
||||
expect(result.disable).toBe(true);
|
||||
expect(result.noUnattended).toBe(true);
|
||||
expect(result.setToken).toEqual({ channelId: 'discord', token: 'abc' });
|
||||
expect(result.setTokenChannel).toBe('discord');
|
||||
expect(clearAll.clearTokenAll).toBe(true);
|
||||
expect(clearOne.clearTokenChannel).toBe('discord');
|
||||
});
|
||||
|
||||
Reference in New Issue
Block a user