mirror of
https://github.com/logto-io/logto.git
synced 2024-12-30 20:33:54 -05:00
refactor(schemas,core,console): unify the usage of OIDC "keyType" in both frontend and backend (#4670)
This commit is contained in:
parent
81e8a2641a
commit
b991597342
5 changed files with 57 additions and 40 deletions
|
@ -1,7 +1,7 @@
|
||||||
import {
|
import {
|
||||||
LogtoOidcConfigKey,
|
|
||||||
SupportedSigningKeyAlgorithm,
|
SupportedSigningKeyAlgorithm,
|
||||||
type OidcConfigKeysResponse,
|
type OidcConfigKeysResponse,
|
||||||
|
LogtoOidcConfigKeyType,
|
||||||
} from '@logto/schemas';
|
} from '@logto/schemas';
|
||||||
import { condArray } from '@silverhand/essentials';
|
import { condArray } from '@silverhand/essentials';
|
||||||
import { useMemo, useState } from 'react';
|
import { useMemo, useState } from 'react';
|
||||||
|
@ -26,9 +26,10 @@ import * as styles from './index.module.scss';
|
||||||
function SigningKeys() {
|
function SigningKeys() {
|
||||||
const api = useApi();
|
const api = useApi();
|
||||||
const { t } = useTranslation(undefined, { keyPrefix: 'admin_console.tenants.signing_keys' });
|
const { t } = useTranslation(undefined, { keyPrefix: 'admin_console.tenants.signing_keys' });
|
||||||
const [activeTab, setActiveTab] = useState<LogtoOidcConfigKey>(LogtoOidcConfigKey.PrivateKeys);
|
const [keyType, setKeyType] = useState<LogtoOidcConfigKeyType>(
|
||||||
const isPrivateKey = activeTab === LogtoOidcConfigKey.PrivateKeys;
|
LogtoOidcConfigKeyType.PrivateKeys
|
||||||
const keyType = isPrivateKey ? 'private-keys' : 'cookie-keys';
|
);
|
||||||
|
const isPrivateKey = keyType === LogtoOidcConfigKeyType.PrivateKeys;
|
||||||
|
|
||||||
const { data, error, mutate } = useSWR<OidcConfigKeysResponse[], RequestError>(
|
const { data, error, mutate } = useSWR<OidcConfigKeysResponse[], RequestError>(
|
||||||
`api/configs/oidc/${keyType}`
|
`api/configs/oidc/${keyType}`
|
||||||
|
@ -97,17 +98,17 @@ function SigningKeys() {
|
||||||
<FormCard title="tenants.signing_keys.title" description="tenants.signing_keys.description">
|
<FormCard title="tenants.signing_keys.title" description="tenants.signing_keys.description">
|
||||||
<TabNav>
|
<TabNav>
|
||||||
<TabNavItem
|
<TabNavItem
|
||||||
isActive={activeTab === LogtoOidcConfigKey.PrivateKeys}
|
isActive={keyType === LogtoOidcConfigKeyType.PrivateKeys}
|
||||||
onClick={() => {
|
onClick={() => {
|
||||||
setActiveTab(LogtoOidcConfigKey.PrivateKeys);
|
setKeyType(LogtoOidcConfigKeyType.PrivateKeys);
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
<DynamicT forKey="tenants.signing_keys.type.private_key" />
|
<DynamicT forKey="tenants.signing_keys.type.private_key" />
|
||||||
</TabNavItem>
|
</TabNavItem>
|
||||||
<TabNavItem
|
<TabNavItem
|
||||||
isActive={activeTab === LogtoOidcConfigKey.CookieKeys}
|
isActive={keyType === LogtoOidcConfigKeyType.CookieKeys}
|
||||||
onClick={() => {
|
onClick={() => {
|
||||||
setActiveTab(LogtoOidcConfigKey.CookieKeys);
|
setKeyType(LogtoOidcConfigKeyType.CookieKeys);
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
<DynamicT forKey="tenants.signing_keys.type.cookie_key" />
|
<DynamicT forKey="tenants.signing_keys.type.cookie_key" />
|
||||||
|
|
|
@ -11,6 +11,7 @@ import {
|
||||||
SupportedSigningKeyAlgorithm,
|
SupportedSigningKeyAlgorithm,
|
||||||
type OidcConfigKeysResponse,
|
type OidcConfigKeysResponse,
|
||||||
type OidcConfigKey,
|
type OidcConfigKey,
|
||||||
|
LogtoOidcConfigKeyType,
|
||||||
} from '@logto/schemas';
|
} from '@logto/schemas';
|
||||||
import { z } from 'zod';
|
import { z } from 'zod';
|
||||||
|
|
||||||
|
@ -20,19 +21,11 @@ import { exportJWK } from '#src/utils/jwks.js';
|
||||||
|
|
||||||
import type { AuthedRouter, RouterInitArgs } from './types.js';
|
import type { AuthedRouter, RouterInitArgs } from './types.js';
|
||||||
|
|
||||||
/*
|
|
||||||
* Logto OIDC private key type used in API routes
|
|
||||||
*/
|
|
||||||
enum LogtoOidcPrivateKeyType {
|
|
||||||
PrivateKeys = 'private-keys',
|
|
||||||
CookieKeys = 'cookie-keys',
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Provide a simple API router key type and DB column mapping
|
* Provide a simple API router key type and DB config key mapping
|
||||||
*/
|
*/
|
||||||
const getOidcConfigKeyDatabaseColumnName = (key: LogtoOidcPrivateKeyType): LogtoOidcConfigKey =>
|
const getOidcConfigKeyDatabaseColumnName = (key: LogtoOidcConfigKeyType): LogtoOidcConfigKey =>
|
||||||
key === LogtoOidcPrivateKeyType.PrivateKeys
|
key === LogtoOidcConfigKeyType.PrivateKeys
|
||||||
? LogtoOidcConfigKey.PrivateKeys
|
? LogtoOidcConfigKey.PrivateKeys
|
||||||
: LogtoOidcConfigKey.CookieKeys;
|
: LogtoOidcConfigKey.CookieKeys;
|
||||||
|
|
||||||
|
@ -105,7 +98,7 @@ export default function logtoConfigRoutes<T extends AuthedRouter>(
|
||||||
'/configs/oidc/:keyType',
|
'/configs/oidc/:keyType',
|
||||||
koaGuard({
|
koaGuard({
|
||||||
params: z.object({
|
params: z.object({
|
||||||
keyType: z.nativeEnum(LogtoOidcPrivateKeyType),
|
keyType: z.nativeEnum(LogtoOidcConfigKeyType),
|
||||||
}),
|
}),
|
||||||
response: z.array(oidcConfigKeysResponseGuard),
|
response: z.array(oidcConfigKeysResponseGuard),
|
||||||
status: [200, 404],
|
status: [200, 404],
|
||||||
|
@ -131,7 +124,7 @@ export default function logtoConfigRoutes<T extends AuthedRouter>(
|
||||||
'/configs/oidc/:keyType/:keyId',
|
'/configs/oidc/:keyType/:keyId',
|
||||||
koaGuard({
|
koaGuard({
|
||||||
params: z.object({
|
params: z.object({
|
||||||
keyType: z.nativeEnum(LogtoOidcPrivateKeyType),
|
keyType: z.nativeEnum(LogtoOidcConfigKeyType),
|
||||||
keyId: z.string(),
|
keyId: z.string(),
|
||||||
}),
|
}),
|
||||||
status: [204, 404, 422],
|
status: [204, 404, 422],
|
||||||
|
@ -171,7 +164,7 @@ export default function logtoConfigRoutes<T extends AuthedRouter>(
|
||||||
'/configs/oidc/:keyType/rotate',
|
'/configs/oidc/:keyType/rotate',
|
||||||
koaGuard({
|
koaGuard({
|
||||||
params: z.object({
|
params: z.object({
|
||||||
keyType: z.nativeEnum(LogtoOidcPrivateKeyType),
|
keyType: z.nativeEnum(LogtoOidcConfigKeyType),
|
||||||
}),
|
}),
|
||||||
body: z.object({
|
body: z.object({
|
||||||
signingKeyAlgorithm: z.nativeEnum(SupportedSigningKeyAlgorithm).optional(),
|
signingKeyAlgorithm: z.nativeEnum(SupportedSigningKeyAlgorithm).optional(),
|
||||||
|
|
|
@ -2,6 +2,7 @@ import {
|
||||||
SupportedSigningKeyAlgorithm,
|
SupportedSigningKeyAlgorithm,
|
||||||
type AdminConsoleData,
|
type AdminConsoleData,
|
||||||
type OidcConfigKeysResponse,
|
type OidcConfigKeysResponse,
|
||||||
|
type LogtoOidcConfigKeyType,
|
||||||
} from '@logto/schemas';
|
} from '@logto/schemas';
|
||||||
|
|
||||||
import { authedAdminApi } from './api.js';
|
import { authedAdminApi } from './api.js';
|
||||||
|
@ -16,14 +17,14 @@ export const updateAdminConsoleConfig = async (payload: Partial<AdminConsoleData
|
||||||
})
|
})
|
||||||
.json<AdminConsoleData>();
|
.json<AdminConsoleData>();
|
||||||
|
|
||||||
export const getOidcKeys = async (keyType: 'private-keys' | 'cookie-keys') =>
|
export const getOidcKeys = async (keyType: LogtoOidcConfigKeyType) =>
|
||||||
authedAdminApi.get(`configs/oidc/${keyType}`).json<OidcConfigKeysResponse[]>();
|
authedAdminApi.get(`configs/oidc/${keyType}`).json<OidcConfigKeysResponse[]>();
|
||||||
|
|
||||||
export const deleteOidcKey = async (keyType: 'private-keys' | 'cookie-keys', id: string) =>
|
export const deleteOidcKey = async (keyType: LogtoOidcConfigKeyType, id: string) =>
|
||||||
authedAdminApi.delete(`configs/oidc/${keyType}/${id}`);
|
authedAdminApi.delete(`configs/oidc/${keyType}/${id}`);
|
||||||
|
|
||||||
export const rotateOidcKeys = async (
|
export const rotateOidcKeys = async (
|
||||||
keyType: 'private-keys' | 'cookie-keys',
|
keyType: LogtoOidcConfigKeyType,
|
||||||
signingKeyAlgorithm: SupportedSigningKeyAlgorithm = SupportedSigningKeyAlgorithm.EC
|
signingKeyAlgorithm: SupportedSigningKeyAlgorithm = SupportedSigningKeyAlgorithm.EC
|
||||||
) =>
|
) =>
|
||||||
authedAdminApi
|
authedAdminApi
|
||||||
|
|
|
@ -1,4 +1,8 @@
|
||||||
import { SupportedSigningKeyAlgorithm, type AdminConsoleData } from '@logto/schemas';
|
import {
|
||||||
|
SupportedSigningKeyAlgorithm,
|
||||||
|
type AdminConsoleData,
|
||||||
|
LogtoOidcConfigKeyType,
|
||||||
|
} from '@logto/schemas';
|
||||||
|
|
||||||
import {
|
import {
|
||||||
deleteOidcKey,
|
deleteOidcKey,
|
||||||
|
@ -33,8 +37,8 @@ describe('admin console sign-in experience', () => {
|
||||||
});
|
});
|
||||||
|
|
||||||
it('should get OIDC keys successfully', async () => {
|
it('should get OIDC keys successfully', async () => {
|
||||||
const privateKeys = await getOidcKeys('private-keys');
|
const privateKeys = await getOidcKeys(LogtoOidcConfigKeyType.PrivateKeys);
|
||||||
const cookieKeys = await getOidcKeys('cookie-keys');
|
const cookieKeys = await getOidcKeys(LogtoOidcConfigKeyType.CookieKeys);
|
||||||
|
|
||||||
expect(privateKeys).toHaveLength(1);
|
expect(privateKeys).toHaveLength(1);
|
||||||
expect(privateKeys[0]).toMatchObject(
|
expect(privateKeys[0]).toMatchObject(
|
||||||
|
@ -49,24 +53,27 @@ describe('admin console sign-in experience', () => {
|
||||||
});
|
});
|
||||||
|
|
||||||
it('should not be able to delete the only private key', async () => {
|
it('should not be able to delete the only private key', async () => {
|
||||||
const privateKeys = await getOidcKeys('private-keys');
|
const privateKeys = await getOidcKeys(LogtoOidcConfigKeyType.PrivateKeys);
|
||||||
expect(privateKeys).toHaveLength(1);
|
expect(privateKeys).toHaveLength(1);
|
||||||
await expectRejects(deleteOidcKey('private-keys', privateKeys[0]!.id), {
|
await expectRejects(deleteOidcKey(LogtoOidcConfigKeyType.PrivateKeys, privateKeys[0]!.id), {
|
||||||
code: 'oidc.key_required',
|
code: 'oidc.key_required',
|
||||||
statusCode: 422,
|
statusCode: 422,
|
||||||
});
|
});
|
||||||
|
|
||||||
const cookieKeys = await getOidcKeys('cookie-keys');
|
const cookieKeys = await getOidcKeys(LogtoOidcConfigKeyType.CookieKeys);
|
||||||
expect(cookieKeys).toHaveLength(1);
|
expect(cookieKeys).toHaveLength(1);
|
||||||
await expectRejects(deleteOidcKey('cookie-keys', cookieKeys[0]!.id), {
|
await expectRejects(deleteOidcKey(LogtoOidcConfigKeyType.CookieKeys, cookieKeys[0]!.id), {
|
||||||
code: 'oidc.key_required',
|
code: 'oidc.key_required',
|
||||||
statusCode: 422,
|
statusCode: 422,
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
it('should rotate OIDC keys successfully', async () => {
|
it('should rotate OIDC keys successfully', async () => {
|
||||||
const existingPrivateKeys = await getOidcKeys('private-keys');
|
const existingPrivateKeys = await getOidcKeys(LogtoOidcConfigKeyType.PrivateKeys);
|
||||||
const newPrivateKeys = await rotateOidcKeys('private-keys', SupportedSigningKeyAlgorithm.RSA);
|
const newPrivateKeys = await rotateOidcKeys(
|
||||||
|
LogtoOidcConfigKeyType.PrivateKeys,
|
||||||
|
SupportedSigningKeyAlgorithm.RSA
|
||||||
|
);
|
||||||
|
|
||||||
expect(newPrivateKeys).toHaveLength(2);
|
expect(newPrivateKeys).toHaveLength(2);
|
||||||
expect(newPrivateKeys).toMatchObject([
|
expect(newPrivateKeys).toMatchObject([
|
||||||
|
@ -77,8 +84,8 @@ describe('admin console sign-in experience', () => {
|
||||||
]);
|
]);
|
||||||
expect(newPrivateKeys[1]?.id).toBe(existingPrivateKeys[0]?.id);
|
expect(newPrivateKeys[1]?.id).toBe(existingPrivateKeys[0]?.id);
|
||||||
|
|
||||||
const existingCookieKeys = await getOidcKeys('cookie-keys');
|
const existingCookieKeys = await getOidcKeys(LogtoOidcConfigKeyType.CookieKeys);
|
||||||
const newCookieKeys = await rotateOidcKeys('cookie-keys');
|
const newCookieKeys = await rotateOidcKeys(LogtoOidcConfigKeyType.CookieKeys);
|
||||||
|
|
||||||
expect(newCookieKeys).toHaveLength(2);
|
expect(newCookieKeys).toHaveLength(2);
|
||||||
expect(newCookieKeys).toMatchObject([
|
expect(newCookieKeys).toMatchObject([
|
||||||
|
@ -91,7 +98,7 @@ describe('admin console sign-in experience', () => {
|
||||||
});
|
});
|
||||||
|
|
||||||
it('should only keep 2 recent OIDC keys', async () => {
|
it('should only keep 2 recent OIDC keys', async () => {
|
||||||
const privateKeys = await rotateOidcKeys('private-keys'); // Defaults to 'EC' algorithm
|
const privateKeys = await rotateOidcKeys(LogtoOidcConfigKeyType.PrivateKeys); // Defaults to 'EC' algorithm
|
||||||
|
|
||||||
expect(privateKeys).toHaveLength(2);
|
expect(privateKeys).toHaveLength(2);
|
||||||
expect(privateKeys).toMatchObject([
|
expect(privateKeys).toMatchObject([
|
||||||
|
@ -101,7 +108,10 @@ describe('admin console sign-in experience', () => {
|
||||||
{ id: expect.any(String), signingKeyAlgorithm: 'RSA', createdAt: expect.any(Number) },
|
{ id: expect.any(String), signingKeyAlgorithm: 'RSA', createdAt: expect.any(Number) },
|
||||||
]);
|
]);
|
||||||
|
|
||||||
const privateKeys2 = await rotateOidcKeys('private-keys', SupportedSigningKeyAlgorithm.RSA);
|
const privateKeys2 = await rotateOidcKeys(
|
||||||
|
LogtoOidcConfigKeyType.PrivateKeys,
|
||||||
|
SupportedSigningKeyAlgorithm.RSA
|
||||||
|
);
|
||||||
|
|
||||||
expect(privateKeys2).toHaveLength(2);
|
expect(privateKeys2).toHaveLength(2);
|
||||||
expect(privateKeys2).toMatchObject([
|
expect(privateKeys2).toMatchObject([
|
||||||
|
|
|
@ -1,13 +1,25 @@
|
||||||
import type { ZodType } from 'zod';
|
import type { ZodType } from 'zod';
|
||||||
import { z } from 'zod';
|
import { z } from 'zod';
|
||||||
|
|
||||||
/* --- Logto OIDC configs --- */
|
/**
|
||||||
|
* Logto OIDC signing key types, used mainly in REST API routes.
|
||||||
|
*/
|
||||||
|
export enum LogtoOidcConfigKeyType {
|
||||||
|
PrivateKeys = 'private-keys',
|
||||||
|
CookieKeys = 'cookie-keys',
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Value maps to config key names in `logto_configs` table. Used mainly in DB SQL related scenarios.
|
||||||
|
*/
|
||||||
export enum LogtoOidcConfigKey {
|
export enum LogtoOidcConfigKey {
|
||||||
PrivateKeys = 'oidc.privateKeys',
|
PrivateKeys = 'oidc.privateKeys',
|
||||||
CookieKeys = 'oidc.cookieKeys',
|
CookieKeys = 'oidc.cookieKeys',
|
||||||
}
|
}
|
||||||
|
|
||||||
/* --- Logto supported JWK signing key types --- */
|
/**
|
||||||
|
* Logto supported signing key algorithms for OIDC private keys that sign JWT tokens.
|
||||||
|
*/
|
||||||
export enum SupportedSigningKeyAlgorithm {
|
export enum SupportedSigningKeyAlgorithm {
|
||||||
RSA = 'RSA',
|
RSA = 'RSA',
|
||||||
EC = 'EC',
|
EC = 'EC',
|
||||||
|
|
Loading…
Reference in a new issue