@logto/cli 1.37.1 → 1.39.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/lib/commands/database/alteration/index.js +6 -0
- package/lib/commands/database/config-rotation.d.ts +25 -0
- package/lib/commands/database/config-rotation.js +111 -0
- package/lib/commands/database/config-rotation.test.d.ts +1 -0
- package/lib/commands/database/config-rotation.test.js +36 -0
- package/lib/commands/database/config.js +39 -37
- package/lib/commands/database/seed/oidc-config.d.ts +3 -1
- package/lib/commands/database/seed/oidc-config.js +7 -6
- package/lib/package-json.d.ts +1 -0
- package/lib/package-json.js +3 -2
- package/package.json +7 -6
|
@@ -27,6 +27,12 @@ export const getAvailableAlterations = async (pool, compareMode = 'gt') => {
|
|
|
27
27
|
? getTimestampFromFilename(filename) > databaseTimestamp
|
|
28
28
|
: getTimestampFromFilename(filename) <= databaseTimestamp);
|
|
29
29
|
};
|
|
30
|
+
/**
|
|
31
|
+
* IMPORTANT: Logto Cloud has a parallel implementation of this logic in `CoreAlterationClient`
|
|
32
|
+
* (logto-cloud repo: `packages/azure-functions/src/utils/core-alteration-client.ts`).
|
|
33
|
+
* Any changes to the alteration execution behavior here (e.g., new hooks, execution order)
|
|
34
|
+
* must be synchronized with the Cloud implementation.
|
|
35
|
+
*/
|
|
30
36
|
const deployAlteration = async (pool, { path: filePath, filename }, action = 'up') => {
|
|
31
37
|
const { up, down, beforeUp, beforeDown } = await importAlterationScript(filePath);
|
|
32
38
|
const timestamp = getTimestampFromFilename(filename);
|
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
import { LogtoOidcConfigKey, type SupportedSigningKeyAlgorithm, type OidcConfigKey, type OidcPrivateKey } from '@logto/schemas';
|
|
2
|
+
import type { CommonQueryMethods } from '@silverhand/slonik';
|
|
3
|
+
type RotateConfigResult = {
|
|
4
|
+
keys: OidcPrivateKey[] | OidcConfigKey[];
|
|
5
|
+
};
|
|
6
|
+
export declare const parseRotationGracePeriod: (value?: string) => number | undefined;
|
|
7
|
+
export declare const getEffectiveRotationGracePeriod: ({ key, gracePeriod, envGracePeriod, }: {
|
|
8
|
+
key: LogtoOidcConfigKey.PrivateKeys | LogtoOidcConfigKey.CookieKeys;
|
|
9
|
+
gracePeriod?: number;
|
|
10
|
+
envGracePeriod?: string;
|
|
11
|
+
}) => number;
|
|
12
|
+
export declare const rotateConfigKey: ({ connection, tenantId, key, privateKeyType, gracePeriod, }: {
|
|
13
|
+
connection: CommonQueryMethods;
|
|
14
|
+
tenantId: string;
|
|
15
|
+
key: LogtoOidcConfigKey.PrivateKeys | LogtoOidcConfigKey.CookieKeys;
|
|
16
|
+
privateKeyType: SupportedSigningKeyAlgorithm;
|
|
17
|
+
gracePeriod: number;
|
|
18
|
+
}) => Promise<RotateConfigResult>;
|
|
19
|
+
export declare const trimConfigKey: ({ connection, tenantId, key, length, }: {
|
|
20
|
+
connection: CommonQueryMethods;
|
|
21
|
+
tenantId: string;
|
|
22
|
+
key: LogtoOidcConfigKey.PrivateKeys | LogtoOidcConfigKey.CookieKeys;
|
|
23
|
+
length: number;
|
|
24
|
+
}) => Promise<RotateConfigResult>;
|
|
25
|
+
export {};
|
|
@@ -0,0 +1,111 @@
|
|
|
1
|
+
import { getSeededOidcPrivateKeys, getImmediatelyRotatedOidcPrivateKeys, getRotationStateForCacheInvalidation, getRotationStateForStagedRotation, getStagedRotatedOidcPrivateKeys, getTrimmedOidcPrivateKeys, LogtoOidcConfigKey, LogtoTenantConfigKey, normalizeOidcPrivateKeys, logtoConfigGuards, } from '@logto/schemas';
|
|
2
|
+
import { getRowsByKeys, updateValueByKey } from '../../queries/logto-config.js';
|
|
3
|
+
import { consoleLog } from '../../utils.js';
|
|
4
|
+
import { generateOidcCookieKey, generateOidcPrivateKey } from './utils.js';
|
|
5
|
+
export const parseRotationGracePeriod = (value) => {
|
|
6
|
+
if (value === undefined || value.trim() === '') {
|
|
7
|
+
return;
|
|
8
|
+
}
|
|
9
|
+
const parsed = Number(value);
|
|
10
|
+
if (!Number.isInteger(parsed) || parsed < 0) {
|
|
11
|
+
throw new TypeError('Invalid PRIVATE_KEY_ROTATION_GRACE_PERIOD env value');
|
|
12
|
+
}
|
|
13
|
+
return parsed;
|
|
14
|
+
};
|
|
15
|
+
export const getEffectiveRotationGracePeriod = ({ key, gracePeriod, envGracePeriod, }) => {
|
|
16
|
+
if (key === LogtoOidcConfigKey.CookieKeys) {
|
|
17
|
+
if (gracePeriod !== undefined) {
|
|
18
|
+
throw new TypeError(`${LogtoOidcConfigKey.CookieKeys} does not support grace period`);
|
|
19
|
+
}
|
|
20
|
+
return 0;
|
|
21
|
+
}
|
|
22
|
+
if (gracePeriod !== undefined) {
|
|
23
|
+
if (!Number.isInteger(gracePeriod) || gracePeriod < 0) {
|
|
24
|
+
throw new TypeError('Invalid grace period provided');
|
|
25
|
+
}
|
|
26
|
+
return gracePeriod;
|
|
27
|
+
}
|
|
28
|
+
return parseRotationGracePeriod(envGracePeriod) ?? 0;
|
|
29
|
+
};
|
|
30
|
+
const getRotationStateRow = (rows) => {
|
|
31
|
+
const row = rows.find(({ key }) => key === LogtoTenantConfigKey.SigningKeyRotationState);
|
|
32
|
+
if (!row) {
|
|
33
|
+
return undefined;
|
|
34
|
+
}
|
|
35
|
+
const parsed = logtoConfigGuards[LogtoTenantConfigKey.SigningKeyRotationState].safeParse(row.value);
|
|
36
|
+
return parsed.success ? parsed.data : undefined;
|
|
37
|
+
};
|
|
38
|
+
export const rotateConfigKey = async ({ connection, tenantId, key, privateKeyType, gracePeriod, }) => {
|
|
39
|
+
const { rows } = await getRowsByKeys(connection, tenantId, [
|
|
40
|
+
key,
|
|
41
|
+
LogtoTenantConfigKey.SigningKeyRotationState,
|
|
42
|
+
]);
|
|
43
|
+
const configRow = rows.find((row) => row.key === key);
|
|
44
|
+
const currentRotationState = getRotationStateRow(rows);
|
|
45
|
+
if (!configRow) {
|
|
46
|
+
consoleLog.warn('No key found, create a new one');
|
|
47
|
+
}
|
|
48
|
+
switch (key) {
|
|
49
|
+
case LogtoOidcConfigKey.PrivateKeys: {
|
|
50
|
+
const parsed = logtoConfigGuards[key].safeParse(configRow?.value);
|
|
51
|
+
const original = parsed.success ? parsed.data : [];
|
|
52
|
+
const hasExistingPrivateKeys = original.length > 0;
|
|
53
|
+
const newPrivateKey = await generateOidcPrivateKey(privateKeyType);
|
|
54
|
+
const rotatedPrivateKeys = hasExistingPrivateKeys
|
|
55
|
+
? gracePeriod === 0
|
|
56
|
+
? getImmediatelyRotatedOidcPrivateKeys(original, newPrivateKey)
|
|
57
|
+
: getStagedRotatedOidcPrivateKeys(original, newPrivateKey)
|
|
58
|
+
: getSeededOidcPrivateKeys([newPrivateKey]);
|
|
59
|
+
const rotationState = !hasExistingPrivateKeys || gracePeriod === 0
|
|
60
|
+
? getRotationStateForCacheInvalidation(currentRotationState)
|
|
61
|
+
: getRotationStateForStagedRotation(currentRotationState, gracePeriod);
|
|
62
|
+
await updateValueByKey(connection, tenantId, key, rotatedPrivateKeys);
|
|
63
|
+
await updateValueByKey(connection, tenantId, LogtoTenantConfigKey.SigningKeyRotationState, rotationState);
|
|
64
|
+
return { keys: rotatedPrivateKeys };
|
|
65
|
+
}
|
|
66
|
+
case LogtoOidcConfigKey.CookieKeys: {
|
|
67
|
+
const parsed = logtoConfigGuards[key].safeParse(configRow?.value);
|
|
68
|
+
const original = parsed.success ? parsed.data : [];
|
|
69
|
+
const rotatedCookieKeys = [generateOidcCookieKey(), ...original];
|
|
70
|
+
const rotationState = getRotationStateForCacheInvalidation(currentRotationState);
|
|
71
|
+
await updateValueByKey(connection, tenantId, key, rotatedCookieKeys);
|
|
72
|
+
await updateValueByKey(connection, tenantId, LogtoTenantConfigKey.SigningKeyRotationState, rotationState);
|
|
73
|
+
return { keys: rotatedCookieKeys };
|
|
74
|
+
}
|
|
75
|
+
}
|
|
76
|
+
};
|
|
77
|
+
export const trimConfigKey = async ({ connection, tenantId, key, length, }) => {
|
|
78
|
+
const { rows } = await getRowsByKeys(connection, tenantId, [
|
|
79
|
+
key,
|
|
80
|
+
LogtoTenantConfigKey.SigningKeyRotationState,
|
|
81
|
+
]);
|
|
82
|
+
const configRow = rows.find((row) => row.key === key);
|
|
83
|
+
const currentRotationState = getRotationStateRow(rows);
|
|
84
|
+
if (!configRow) {
|
|
85
|
+
consoleLog.fatal('No key found');
|
|
86
|
+
}
|
|
87
|
+
switch (key) {
|
|
88
|
+
case LogtoOidcConfigKey.PrivateKeys: {
|
|
89
|
+
const value = normalizeOidcPrivateKeys(logtoConfigGuards[key].parse(configRow.value));
|
|
90
|
+
const trimmedPrivateKeys = getTrimmedOidcPrivateKeys(value, length);
|
|
91
|
+
const rotationState = getRotationStateForCacheInvalidation(currentRotationState);
|
|
92
|
+
if (trimmedPrivateKeys.length === 0) {
|
|
93
|
+
consoleLog.fatal(`You should keep at least one key in the array, current length=${value.length}`);
|
|
94
|
+
}
|
|
95
|
+
await updateValueByKey(connection, tenantId, key, trimmedPrivateKeys);
|
|
96
|
+
await updateValueByKey(connection, tenantId, LogtoTenantConfigKey.SigningKeyRotationState, rotationState);
|
|
97
|
+
return { keys: trimmedPrivateKeys };
|
|
98
|
+
}
|
|
99
|
+
case LogtoOidcConfigKey.CookieKeys: {
|
|
100
|
+
const value = logtoConfigGuards[key].parse(configRow.value);
|
|
101
|
+
const rotationState = getRotationStateForCacheInvalidation(currentRotationState);
|
|
102
|
+
if (value.length - length < 1) {
|
|
103
|
+
consoleLog.fatal(`You should keep at least one key in the array, current length=${value.length}`);
|
|
104
|
+
}
|
|
105
|
+
const trimmedCookieKeys = value.slice(0, -length);
|
|
106
|
+
await updateValueByKey(connection, tenantId, key, trimmedCookieKeys);
|
|
107
|
+
await updateValueByKey(connection, tenantId, LogtoTenantConfigKey.SigningKeyRotationState, rotationState);
|
|
108
|
+
return { keys: trimmedCookieKeys };
|
|
109
|
+
}
|
|
110
|
+
}
|
|
111
|
+
};
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export {};
|
|
@@ -0,0 +1,36 @@
|
|
|
1
|
+
import { describe, expect, it } from 'vitest';
|
|
2
|
+
const { getEffectiveRotationGracePeriod, parseRotationGracePeriod } = await import('./config-rotation.js');
|
|
3
|
+
const { LogtoOidcConfigKey } = await import('@logto/schemas');
|
|
4
|
+
describe('parseRotationGracePeriod()', () => {
|
|
5
|
+
it('returns undefined when env is not set', () => {
|
|
6
|
+
expect(parseRotationGracePeriod()).toBeUndefined();
|
|
7
|
+
expect(parseRotationGracePeriod(' ')).toBeUndefined();
|
|
8
|
+
});
|
|
9
|
+
it('parses a valid non-negative integer', () => {
|
|
10
|
+
expect(parseRotationGracePeriod('14400')).toBe(14_400);
|
|
11
|
+
});
|
|
12
|
+
it('throws on malformed input instead of silently falling back to 0', () => {
|
|
13
|
+
expect(() => parseRotationGracePeriod('4h')).toThrow('Invalid PRIVATE_KEY_ROTATION_GRACE_PERIOD env value');
|
|
14
|
+
expect(() => parseRotationGracePeriod('-1')).toThrow('Invalid PRIVATE_KEY_ROTATION_GRACE_PERIOD env value');
|
|
15
|
+
});
|
|
16
|
+
});
|
|
17
|
+
describe('getEffectiveRotationGracePeriod()', () => {
|
|
18
|
+
it('uses the explicit private-key grace period when provided', () => {
|
|
19
|
+
expect(getEffectiveRotationGracePeriod({
|
|
20
|
+
key: LogtoOidcConfigKey.PrivateKeys,
|
|
21
|
+
gracePeriod: 60,
|
|
22
|
+
})).toBe(60);
|
|
23
|
+
});
|
|
24
|
+
it('uses the env default for private keys when no explicit grace period is provided', () => {
|
|
25
|
+
expect(getEffectiveRotationGracePeriod({
|
|
26
|
+
key: LogtoOidcConfigKey.PrivateKeys,
|
|
27
|
+
envGracePeriod: '120',
|
|
28
|
+
})).toBe(120);
|
|
29
|
+
});
|
|
30
|
+
it('rejects cookie-key grace period even when the explicit value is 0', () => {
|
|
31
|
+
expect(() => getEffectiveRotationGracePeriod({
|
|
32
|
+
key: LogtoOidcConfigKey.CookieKeys,
|
|
33
|
+
gracePeriod: 0,
|
|
34
|
+
})).toThrow(`${LogtoOidcConfigKey.CookieKeys} does not support grace period`);
|
|
35
|
+
});
|
|
36
|
+
});
|
|
@@ -4,7 +4,7 @@ import chalk from 'chalk';
|
|
|
4
4
|
import { createPoolFromConfig } from '../../database.js';
|
|
5
5
|
import { getRowsByKeys, updateValueByKey } from '../../queries/logto-config.js';
|
|
6
6
|
import { consoleLog } from '../../utils.js';
|
|
7
|
-
import {
|
|
7
|
+
import { getEffectiveRotationGracePeriod, rotateConfigKey, trimConfigKey, } from './config-rotation.js';
|
|
8
8
|
const validKeysDisplay = chalk.green(logtoConfigKeys.join(', '));
|
|
9
9
|
const validateKeys = (keys) => {
|
|
10
10
|
const invalidKey = (Array.isArray(keys) ? keys : [keys]).find(
|
|
@@ -37,6 +37,10 @@ const validatePrivateKeyType = (key) => {
|
|
|
37
37
|
consoleLog.fatal(`Invalid private key type ${chalk.red(key)} found, expected one of ${validPrivateKeyTypes.join(', ')}`);
|
|
38
38
|
}
|
|
39
39
|
};
|
|
40
|
+
const getValidatedPrivateKeyType = (key) => {
|
|
41
|
+
validatePrivateKeyType(key);
|
|
42
|
+
return key;
|
|
43
|
+
};
|
|
40
44
|
const getConfig = {
|
|
41
45
|
command: 'get <key> [keys...]',
|
|
42
46
|
describe: 'Get config value(s) of the given key(s) in Logto database',
|
|
@@ -103,7 +107,7 @@ const setConfig = {
|
|
|
103
107
|
};
|
|
104
108
|
const rotateConfig = {
|
|
105
109
|
command: 'rotate <key>',
|
|
106
|
-
describe: 'Generate a new private or secret key for the given config key and
|
|
110
|
+
describe: 'Generate a new private or secret key for the given config key and update the key state',
|
|
107
111
|
builder: (yargs) => yargs
|
|
108
112
|
.positional('key', {
|
|
109
113
|
describe: `The key to rotate, one of ${chalk.green(validRotateKeys.join(', '))}`,
|
|
@@ -119,31 +123,37 @@ const rotateConfig = {
|
|
|
119
123
|
describe: `The key type for ${LogtoOidcConfigKey.PrivateKeys}, one of ${validPrivateKeyTypes.join(', ')}`,
|
|
120
124
|
type: 'string',
|
|
121
125
|
default: 'ec',
|
|
126
|
+
})
|
|
127
|
+
.option('gracePeriod', {
|
|
128
|
+
describe: `Grace period in seconds for ${LogtoOidcConfigKey.PrivateKeys} staged rotation`,
|
|
129
|
+
type: 'number',
|
|
122
130
|
}),
|
|
123
|
-
handler: async ({ key, tenantId, type }) => {
|
|
131
|
+
handler: async ({ key, tenantId, type, gracePeriod }) => {
|
|
124
132
|
const keyType = type.toUpperCase();
|
|
133
|
+
const privateKeyType = key === LogtoOidcConfigKey.PrivateKeys
|
|
134
|
+
? getValidatedPrivateKeyType(keyType)
|
|
135
|
+
: SupportedSigningKeyAlgorithm.EC;
|
|
125
136
|
validateRotateKey(key);
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
const parsed = logtoConfigGuards[key].safeParse(rows[0]?.value);
|
|
134
|
-
const original = parsed.success ? parsed.data : [];
|
|
135
|
-
// No need for default. It's already exhaustive
|
|
136
|
-
switch (key) {
|
|
137
|
-
case LogtoOidcConfigKey.PrivateKeys: {
|
|
138
|
-
return [await generateOidcPrivateKey(keyType), ...original];
|
|
139
|
-
}
|
|
140
|
-
case LogtoOidcConfigKey.CookieKeys: {
|
|
141
|
-
return [generateOidcCookieKey(), ...original];
|
|
142
|
-
}
|
|
137
|
+
const effectiveGracePeriod = (() => {
|
|
138
|
+
try {
|
|
139
|
+
return getEffectiveRotationGracePeriod({
|
|
140
|
+
key,
|
|
141
|
+
gracePeriod,
|
|
142
|
+
envGracePeriod: process.env.PRIVATE_KEY_ROTATION_GRACE_PERIOD,
|
|
143
|
+
});
|
|
143
144
|
}
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
145
|
+
catch (error) {
|
|
146
|
+
return consoleLog.fatal(error instanceof Error ? error.message : 'Invalid grace period provided');
|
|
147
|
+
}
|
|
148
|
+
})();
|
|
149
|
+
const pool = await createPoolFromConfig();
|
|
150
|
+
const { keys: rotated } = await pool.transaction(async (connection) => rotateConfigKey({
|
|
151
|
+
connection,
|
|
152
|
+
tenantId,
|
|
153
|
+
key,
|
|
154
|
+
privateKeyType,
|
|
155
|
+
gracePeriod: effectiveGracePeriod,
|
|
156
|
+
}));
|
|
147
157
|
await pool.end();
|
|
148
158
|
consoleLog.info(`Rotate ${chalk.green(key)} succeeded, now it has ${rotated.length} keys`);
|
|
149
159
|
},
|
|
@@ -174,20 +184,12 @@ const trimConfig = {
|
|
|
174
184
|
consoleLog.fatal('Invalid length provided');
|
|
175
185
|
}
|
|
176
186
|
const pool = await createPoolFromConfig();
|
|
177
|
-
const {
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
if (value.length - length < 1) {
|
|
184
|
-
await pool.end();
|
|
185
|
-
consoleLog.fatal(`You should keep at least one key in the array, current length=${value.length}`);
|
|
186
|
-
}
|
|
187
|
-
return value.slice(0, -length);
|
|
188
|
-
};
|
|
189
|
-
const trimmed = await getValue();
|
|
190
|
-
await updateValueByKey(pool, tenantId, key, trimmed);
|
|
187
|
+
const { keys: trimmed } = await pool.transaction(async (connection) => trimConfigKey({
|
|
188
|
+
connection,
|
|
189
|
+
tenantId,
|
|
190
|
+
key,
|
|
191
|
+
length,
|
|
192
|
+
}));
|
|
191
193
|
await pool.end();
|
|
192
194
|
consoleLog.info(`Trim ${chalk.green(key)} succeeded, now it has ${trimmed.length} keys`);
|
|
193
195
|
},
|
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import type { LogtoOidcConfigType } from '@logto/schemas';
|
|
2
2
|
import { LogtoOidcConfigKey } from '@logto/schemas';
|
|
3
3
|
import type { DatabaseTransactionConnection } from '@silverhand/slonik';
|
|
4
|
+
type OidcConfigKeyToSeed = Exclude<LogtoOidcConfigKey, LogtoOidcConfigKey.Session>;
|
|
4
5
|
export declare const seedOidcConfigs: (pool: DatabaseTransactionConnection, tenantId: string) => Promise<void>;
|
|
5
6
|
/**
|
|
6
7
|
* Each config reader will do the following things in order:
|
|
@@ -8,8 +9,9 @@ export declare const seedOidcConfigs: (pool: DatabaseTransactionConnection, tena
|
|
|
8
9
|
* 2. Generate value if #1 doesn't work
|
|
9
10
|
*/
|
|
10
11
|
export declare const oidcConfigReaders: {
|
|
11
|
-
[key in
|
|
12
|
+
[key in OidcConfigKeyToSeed]: () => Promise<{
|
|
12
13
|
value: LogtoOidcConfigType[key];
|
|
13
14
|
fromEnv: boolean;
|
|
14
15
|
}>;
|
|
15
16
|
};
|
|
17
|
+
export {};
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { readFile } from 'node:fs/promises';
|
|
2
|
-
import { LogtoOidcConfigKey, logtoConfigGuards } from '@logto/schemas';
|
|
2
|
+
import { LogtoOidcConfigKey, getSeededOidcPrivateKeys, logtoConfigGuards } from '@logto/schemas';
|
|
3
3
|
import { generateStandardId } from '@logto/shared';
|
|
4
4
|
import { getEnvAsStringArray } from '@silverhand/essentials';
|
|
5
5
|
import chalk from 'chalk';
|
|
@@ -8,13 +8,14 @@ import { getRowsByKeys, updateValueByKey } from '../../../queries/logto-config.j
|
|
|
8
8
|
import { consoleLog } from '../../../utils.js';
|
|
9
9
|
import { buildOidcKeyFromRawString, generateOidcCookieKey, generateOidcPrivateKey, } from '../utils.js';
|
|
10
10
|
const isBase64FormatPrivateKey = (key) => !key.includes('-');
|
|
11
|
+
const oidcConfigKeysToSeed = Object.values(LogtoOidcConfigKey).filter((key) => key !== LogtoOidcConfigKey.Session);
|
|
11
12
|
export const seedOidcConfigs = async (pool, tenantId) => {
|
|
12
13
|
const tenantPrefix = `[${tenantId}]`;
|
|
13
14
|
const configGuard = z.object({
|
|
14
15
|
key: z.nativeEnum(LogtoOidcConfigKey),
|
|
15
16
|
value: z.unknown(),
|
|
16
17
|
});
|
|
17
|
-
const { rows } = await getRowsByKeys(pool, tenantId,
|
|
18
|
+
const { rows } = await getRowsByKeys(pool, tenantId, oidcConfigKeysToSeed);
|
|
18
19
|
// Filter out valid keys that hold a valid value
|
|
19
20
|
const result = await Promise.all(rows.map(async (row) => {
|
|
20
21
|
try {
|
|
@@ -25,7 +26,7 @@ export const seedOidcConfigs = async (pool, tenantId) => {
|
|
|
25
26
|
catch { }
|
|
26
27
|
}));
|
|
27
28
|
const existingKeys = new Set(result.filter(Boolean));
|
|
28
|
-
const validOptions =
|
|
29
|
+
const validOptions = oidcConfigKeysToSeed.filter((key) => {
|
|
29
30
|
const included = existingKeys.has(key);
|
|
30
31
|
if (included) {
|
|
31
32
|
consoleLog.info(tenantPrefix, `Key ${chalk.green(key)} exists, skipping`);
|
|
@@ -68,7 +69,7 @@ export const oidcConfigReaders = {
|
|
|
68
69
|
const privateKeys = getEnvAsStringArray('OIDC_PRIVATE_KEYS');
|
|
69
70
|
if (privateKeys.length > 0) {
|
|
70
71
|
return {
|
|
71
|
-
value: privateKeys.map((key) => buildOidcKeyFromRawString(isBase64FormatPrivateKey(key) ? Buffer.from(key, 'base64').toString('utf8') : key)),
|
|
72
|
+
value: getSeededOidcPrivateKeys(privateKeys.map((key) => buildOidcKeyFromRawString(isBase64FormatPrivateKey(key) ? Buffer.from(key, 'base64').toString('utf8') : key))),
|
|
72
73
|
fromEnv: true,
|
|
73
74
|
};
|
|
74
75
|
}
|
|
@@ -77,12 +78,12 @@ export const oidcConfigReaders = {
|
|
|
77
78
|
if (privateKeyPaths.length > 0) {
|
|
78
79
|
const privateKeys = await Promise.all(privateKeyPaths.map(async (path) => readFile(path, 'utf8')));
|
|
79
80
|
return {
|
|
80
|
-
value: privateKeys.map((key) => buildOidcKeyFromRawString(key)),
|
|
81
|
+
value: getSeededOidcPrivateKeys(privateKeys.map((key) => buildOidcKeyFromRawString(key))),
|
|
81
82
|
fromEnv: true,
|
|
82
83
|
};
|
|
83
84
|
}
|
|
84
85
|
return {
|
|
85
|
-
value: [await generateOidcPrivateKey()],
|
|
86
|
+
value: getSeededOidcPrivateKeys([await generateOidcPrivateKey()]),
|
|
86
87
|
fromEnv: false,
|
|
87
88
|
};
|
|
88
89
|
},
|
package/lib/package-json.d.ts
CHANGED
package/lib/package-json.js
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
export const packageJson = {
|
|
2
2
|
"name": "@logto/cli",
|
|
3
|
-
"version": "1.
|
|
3
|
+
"version": "1.39.0",
|
|
4
4
|
"description": "Logto CLI.",
|
|
5
5
|
"author": "Silverhand Inc. <contact@silverhand.io>",
|
|
6
6
|
"homepage": "https://github.com/logto-io/logto#readme",
|
|
@@ -31,7 +31,8 @@ export const packageJson = {
|
|
|
31
31
|
"start:dev": "pnpm build && node .",
|
|
32
32
|
"lint": "eslint --ext .ts src",
|
|
33
33
|
"lint:report": "pnpm lint --format json --output-file report.json",
|
|
34
|
-
"test": "vitest src",
|
|
34
|
+
"test": "vitest run src",
|
|
35
|
+
"test:watch": "vitest src --watch",
|
|
35
36
|
"test:ci": "pnpm run test --silent --coverage",
|
|
36
37
|
"prepack": "pnpm build"
|
|
37
38
|
},
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@logto/cli",
|
|
3
|
-
"version": "1.
|
|
3
|
+
"version": "1.39.0",
|
|
4
4
|
"description": "Logto CLI.",
|
|
5
5
|
"author": "Silverhand Inc. <contact@silverhand.io>",
|
|
6
6
|
"homepage": "https://github.com/logto-io/logto#readme",
|
|
@@ -45,10 +45,10 @@
|
|
|
45
45
|
"tar": "^7.5.7",
|
|
46
46
|
"yargs": "^17.6.0",
|
|
47
47
|
"zod": "3.24.3",
|
|
48
|
-
"@logto/
|
|
49
|
-
"@logto/
|
|
50
|
-
"@logto/
|
|
51
|
-
"@logto/
|
|
48
|
+
"@logto/connector-kit": "^5.0.0",
|
|
49
|
+
"@logto/core-kit": "^2.9.0",
|
|
50
|
+
"@logto/schemas": "1.39.0",
|
|
51
|
+
"@logto/shared": "^3.4.0"
|
|
52
52
|
},
|
|
53
53
|
"devDependencies": {
|
|
54
54
|
"@silverhand/eslint-config": "6.0.1",
|
|
@@ -88,7 +88,8 @@
|
|
|
88
88
|
"start:dev": "pnpm build && node .",
|
|
89
89
|
"lint": "eslint --ext .ts src",
|
|
90
90
|
"lint:report": "pnpm lint --format json --output-file report.json",
|
|
91
|
-
"test": "vitest src",
|
|
91
|
+
"test": "vitest run src",
|
|
92
|
+
"test:watch": "vitest src --watch",
|
|
92
93
|
"test:ci": "pnpm run test --silent --coverage"
|
|
93
94
|
}
|
|
94
95
|
}
|