mirror of
https://github.com/tiennm99/ccs.git
synced 2026-09-02 22:17:14 +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 {
|
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 {
|
export function getOfficialChannelClearTokenHelp(): string {
|
||||||
|
|||||||
@@ -54,31 +54,13 @@ interface ChannelsCommandOptions {
|
|||||||
setSelectionMissing: boolean;
|
setSelectionMissing: boolean;
|
||||||
clearTokenAll: boolean;
|
clearTokenAll: boolean;
|
||||||
clearTokenChannel?: OfficialChannelId;
|
clearTokenChannel?: OfficialChannelId;
|
||||||
setToken?: { channelId: OfficialChannelId; token: string };
|
setTokenChannel?: OfficialChannelId;
|
||||||
setTokenMissing: boolean;
|
setTokenMissing: boolean;
|
||||||
clearTokenInvalid?: string;
|
clearTokenInvalid?: string;
|
||||||
setTokenInvalid?: string;
|
setTokenInvalid?: string;
|
||||||
help: boolean;
|
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 {
|
export function parseChannelsCommandArgs(args: string[]): ChannelsCommandOptions {
|
||||||
const setSelection = extractOption(args, ['--set']);
|
const setSelection = extractOption(args, ['--set']);
|
||||||
const setToken = extractOption(args, ['--set-token']);
|
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;
|
let setTokenInvalid: string | undefined;
|
||||||
if (setToken.found && !setToken.missingValue && setToken.value) {
|
if (setToken.found && !setToken.missingValue && setToken.value) {
|
||||||
parsedSetToken = parseTokenAssignment(setToken.value) ?? undefined;
|
const channelId = setToken.value.trim().toLowerCase();
|
||||||
if (!parsedSetToken) {
|
if (isOfficialChannelId(channelId)) {
|
||||||
|
parsedSetTokenChannel = channelId;
|
||||||
|
} else {
|
||||||
setTokenInvalid = setToken.value;
|
setTokenInvalid = setToken.value;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -120,7 +104,7 @@ export function parseChannelsCommandArgs(args: string[]): ChannelsCommandOptions
|
|||||||
clearTokenAll,
|
clearTokenAll,
|
||||||
clearTokenChannel,
|
clearTokenChannel,
|
||||||
clearTokenInvalid,
|
clearTokenInvalid,
|
||||||
setToken: parsedSetToken,
|
setTokenChannel: parsedSetTokenChannel,
|
||||||
setTokenMissing: setToken.found && setToken.missingValue,
|
setTokenMissing: setToken.found && setToken.missingValue,
|
||||||
setTokenInvalid,
|
setTokenInvalid,
|
||||||
help: hasAnyFlag(args, ['--help', '-h']),
|
help: hasAnyFlag(args, ['--help', '-h']),
|
||||||
@@ -153,7 +137,7 @@ function showHelp(): void {
|
|||||||
` ${color('--unattended', 'command')} Also add --dangerously-skip-permissions`
|
` ${color('--unattended', 'command')} Also add --dangerously-skip-permissions`
|
||||||
);
|
);
|
||||||
console.log(` ${color('--no-unattended', 'command')} Disable unattended runtime flag`);
|
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(
|
console.log(
|
||||||
` ${color('--clear-token [channel]', 'command')} ${getOfficialChannelClearTokenHelp()}`
|
` ${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')}`
|
` $ ${color('ccs config channels --set all', 'command')} ${dim('# Enable all official channels')}`
|
||||||
);
|
);
|
||||||
console.log(
|
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(
|
console.log(
|
||||||
` $ ${color('ccs config channels --clear-token discord', 'command')} ${dim('# Clear one token')}`
|
` $ ${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) {
|
if (options.setTokenInvalid) {
|
||||||
console.error(
|
console.error(
|
||||||
fail(`Invalid --set-token value: ${options.setTokenInvalid} (use <channel>=<token>)`)
|
fail(
|
||||||
|
`Invalid --set-token value: ${options.setTokenInvalid} (use ${getOfficialChannelChoices()})`
|
||||||
|
)
|
||||||
);
|
);
|
||||||
process.exitCode = 1;
|
process.exitCode = 1;
|
||||||
return;
|
return;
|
||||||
@@ -444,12 +430,19 @@ export async function handleConfigChannelsCommand(args: string[]): Promise<void>
|
|||||||
updateConfig({ channels: nextConfig });
|
updateConfig({ channels: nextConfig });
|
||||||
}
|
}
|
||||||
|
|
||||||
if (options.setToken) {
|
if (options.setTokenChannel) {
|
||||||
if (!getOfficialChannelTokenIds().includes(options.setToken.channelId)) {
|
if (!getOfficialChannelTokenIds().includes(options.setTokenChannel)) {
|
||||||
throw new Error(`${options.setToken.channelId} does not use a bot token.`);
|
throw new Error(`${options.setTokenChannel} does not use a bot token.`);
|
||||||
}
|
}
|
||||||
setConfiguredOfficialChannelToken(options.setToken.channelId, options.setToken.token);
|
const envKey = getOfficialChannelEnvKey(options.setTokenChannel);
|
||||||
console.log(ok(`${getOfficialChannelDisplayName(options.setToken.channelId)} token saved`));
|
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('');
|
console.log('');
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -2,35 +2,32 @@ import { describe, expect, it } from 'bun:test';
|
|||||||
import { parseChannelsCommandArgs } from '../../../src/commands/config-channels-command';
|
import { parseChannelsCommandArgs } from '../../../src/commands/config-channels-command';
|
||||||
|
|
||||||
describe('config channels command parser', () => {
|
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([
|
const result = parseChannelsCommandArgs([
|
||||||
'--set',
|
'--set',
|
||||||
'telegram,discord',
|
'telegram,discord',
|
||||||
'--unattended',
|
'--unattended',
|
||||||
'--set-token',
|
'--set-token',
|
||||||
'telegram=telegram-secret',
|
'telegram',
|
||||||
]);
|
]);
|
||||||
|
|
||||||
expect(result.setSelection).toBe('telegram,discord');
|
expect(result.setSelection).toBe('telegram,discord');
|
||||||
expect(result.unattended).toBe(true);
|
expect(result.unattended).toBe(true);
|
||||||
expect(result.setToken).toEqual({
|
expect(result.setTokenChannel).toBe('telegram');
|
||||||
channelId: 'telegram',
|
|
||||||
token: 'telegram-secret',
|
|
||||||
});
|
|
||||||
});
|
});
|
||||||
|
|
||||||
it('supports inline token assignment, legacy flags, and clear-token variants', () => {
|
it('supports legacy flags and clear-token variants', () => {
|
||||||
const result = parseChannelsCommandArgs([
|
const result = parseChannelsCommandArgs([
|
||||||
'--disable',
|
'--disable',
|
||||||
'--no-unattended',
|
'--no-unattended',
|
||||||
'--set-token=abc',
|
'--set-token=discord',
|
||||||
]);
|
]);
|
||||||
const clearAll = parseChannelsCommandArgs(['--clear-token']);
|
const clearAll = parseChannelsCommandArgs(['--clear-token']);
|
||||||
const clearOne = parseChannelsCommandArgs(['--clear-token', 'discord']);
|
const clearOne = parseChannelsCommandArgs(['--clear-token', 'discord']);
|
||||||
|
|
||||||
expect(result.disable).toBe(true);
|
expect(result.disable).toBe(true);
|
||||||
expect(result.noUnattended).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(clearAll.clearTokenAll).toBe(true);
|
||||||
expect(clearOne.clearTokenChannel).toBe('discord');
|
expect(clearOne.clearTokenChannel).toBe('discord');
|
||||||
});
|
});
|
||||||
|
|||||||
Reference in New Issue
Block a user