0
Fork 0
mirror of https://github.com/logto-io/logto.git synced 2025-03-10 22:22:45 -05:00

feat(cli): add key type for rotating command (#4314)

This commit is contained in:
wangsijie 2023-08-14 14:57:26 +08:00 committed by GitHub
parent 1b983c8c89
commit ae0ef919fb
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
3 changed files with 43 additions and 7 deletions

View file

@ -0,0 +1,5 @@
---
"@logto/cli": minor
---
Add key type option for the command of rotating oidc.privateKeys

View file

@ -13,7 +13,7 @@ import { createPoolFromConfig } from '../../database.js';
import { getRowsByKeys, updateValueByKey } from '../../queries/logto-config.js';
import { consoleLog } from '../../utils.js';
import { generateOidcCookieKey, generateOidcPrivateKey } from './utils.js';
import { PrivateKeyType, generateOidcCookieKey, generateOidcPrivateKey } from './utils.js';
const validKeysDisplay = chalk.green(logtoConfigKeys.join(', '));
@ -41,8 +41,14 @@ const validRotateKeys = Object.freeze([
LogtoOidcConfigKey.CookieKeys,
] as const);
const validPrivateKeyTypes = Object.freeze([PrivateKeyType.RSA, PrivateKeyType.EC] as const);
type ValidateRotateKeyFunction = (key: string) => asserts key is (typeof validRotateKeys)[number];
type ValidatePrivateKeyTypeFunction = (
key: string
) => asserts key is (typeof validPrivateKeyTypes)[number];
const validateRotateKey: ValidateRotateKeyFunction = (key) => {
// Using `.includes()` will result a type error
// eslint-disable-next-line unicorn/prefer-includes
@ -53,6 +59,18 @@ const validateRotateKey: ValidateRotateKeyFunction = (key) => {
}
};
const validatePrivateKeyType: ValidatePrivateKeyTypeFunction = (key) => {
// Using `.includes()` will result a type error
// eslint-disable-next-line unicorn/prefer-includes
if (!validPrivateKeyTypes.some((element) => element === key)) {
consoleLog.fatal(
`Invalid private key type ${chalk.red(
key
)} found, expected one of ${validPrivateKeyTypes.join(', ')}`
);
}
};
const getConfig: CommandModule<unknown, { key: string; keys: string[]; tenantId: string }> = {
command: 'get <key> [keys...]',
describe: 'Get config value(s) of the given key(s) in Logto database',
@ -131,7 +149,7 @@ const setConfig: CommandModule<unknown, { key: string; value: string; tenantId:
},
};
const rotateConfig: CommandModule<unknown, { key: string; tenantId: string }> = {
const rotateConfig: CommandModule<unknown, { key: string; tenantId: string; type: string }> = {
command: 'rotate <key>',
describe:
'Generate a new private or secret key for the given config key and prepend to the key array',
@ -146,9 +164,17 @@ const rotateConfig: CommandModule<unknown, { key: string; tenantId: string }> =
describe: 'The tenant to operate',
type: 'string',
default: defaultTenantId,
})
.option('type', {
describe: `The key type for ${
LogtoOidcConfigKey.PrivateKeys
}, one of ${validPrivateKeyTypes.join(', ')}`,
type: 'string',
default: 'ec',
}),
handler: async ({ key, tenantId }) => {
handler: async ({ key, tenantId, type }) => {
validateRotateKey(key);
validatePrivateKeyType(type);
const pool = await createPoolFromConfig();
const { rows } = await getRowsByKeys(pool, tenantId, [key]);
@ -164,7 +190,7 @@ const rotateConfig: CommandModule<unknown, { key: string; tenantId: string }> =
// No need for default. It's already exhaustive
switch (key) {
case LogtoOidcConfigKey.PrivateKeys: {
return [await generateOidcPrivateKey(), ...original];
return [await generateOidcPrivateKey(type), ...original];
}
case LogtoOidcConfigKey.CookieKeys: {

View file

@ -3,8 +3,13 @@ import { promisify } from 'node:util';
import { generateStandardId } from '@logto/shared';
export const generateOidcPrivateKey = async (type: 'rsa' | 'ec' = 'ec') => {
if (type === 'rsa') {
export enum PrivateKeyType {
RSA = 'rsa',
EC = 'ec',
}
export const generateOidcPrivateKey = async (type: PrivateKeyType = PrivateKeyType.EC) => {
if (type === PrivateKeyType.RSA) {
const { privateKey } = await promisify(generateKeyPair)('rsa', {
modulusLength: 4096,
publicKeyEncoding: {
@ -21,7 +26,7 @@ export const generateOidcPrivateKey = async (type: 'rsa' | 'ec' = 'ec') => {
}
// eslint-disable-next-line @typescript-eslint/no-unnecessary-condition
if (type === 'ec') {
if (type === PrivateKeyType.EC) {
const { privateKey } = await promisify(generateKeyPair)('ec', {
// https://security.stackexchange.com/questions/78621/which-elliptic-curve-should-i-use
namedCurve: 'secp384r1',