diff --git a/.changeset/unlucky-lizards-agree.md b/.changeset/unlucky-lizards-agree.md index 3e5bf43eb..af458f4f3 100644 --- a/.changeset/unlucky-lizards-agree.md +++ b/.changeset/unlucky-lizards-agree.md @@ -6,8 +6,18 @@ ### Rotate your private or secret key -Add a new command `db config rotate ` to support key rotation via CLI. +We add a new command `db config rotate ` to support key rotation via CLI. -When rotating, the CLI will generate a new key and prepend to the corresponding key array. Thus the old key is still valid and the serivce will use the new key for signing. +When rotating, the CLI will generate a new key and prepend to the corresponding key array. Thus the old key is still valid and the service will use the new key for signing. Run `logto db config rotate help` for detailed usage. + +### Trim the private or secret key you don't need + +If you want to trim one or more out-dated private or secret key(s) from the config, use the command `db config trim `. It will remove the last item (private or secret key) in the array. + +You may remove the old key after a certain period (such as half a year) to allow most of your users have time to touch the new key. + +If you want to remove multiple keys at once, just append a number to the command. E.g. `logto db config trim oidc.cookieKeys 3`. + +Run `logto db config trim help` for detailed usage. diff --git a/packages/cli/src/commands/database/config.ts b/packages/cli/src/commands/database/config.ts index 9f6c6c540..cb61e1fa5 100644 --- a/packages/cli/src/commands/database/config.ts +++ b/packages/cli/src/commands/database/config.ts @@ -154,11 +154,64 @@ const rotateConfig: CommandModule = { }, }; +const trimConfig: CommandModule = { + command: 'trim [length]', + describe: 'Remove the last [length] number of private or secret keys for the given config key', + builder: (yargs) => + yargs + .positional('key', { + describe: `The config key to trim, one of ${chalk.green(validRotateKeys.join(', '))}`, + type: 'string', + demandOption: true, + }) + .positional('length', { + describe: 'Number of private or secret keys to trim', + type: 'number', + default: 1, + demandOption: true, + }), + handler: async ({ key, length }) => { + validateRotateKey(key); + + if (length < 1) { + log.error('Invalid length provided'); + } + + const pool = await createPoolFromConfig(); + const { rows } = await getRowsByKeys(pool, [key]); + + if (!rows[0]) { + log.warn('No key found, create a new one'); + } + + const getValue = async () => { + const value = logtoConfigGuards[key].parse(rows[0]?.value); + + if (value.length - length < 1) { + await pool.end(); + log.error(`You should keep at least one key in the array, current length=${value.length}`); + } + + return value.slice(0, -length); + }; + const trimmed = await getValue(); + await updateValueByKey(pool, key, trimmed); + await pool.end(); + + log.info(`Trim ${chalk.green(key)} succeeded, now it has ${trimmed.length} keys`); + }, +}; + const config: CommandModule = { command: ['config', 'configs'], describe: 'Commands for Logto database config', builder: (yargs) => - yargs.command(getConfig).command(setConfig).command(rotateConfig).demandCommand(1), + yargs + .command(getConfig) + .command(setConfig) + .command(rotateConfig) + .command(trimConfig) + .demandCommand(1), handler: noop, };