0
Fork 0
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:
Charles Zhao 2023-10-16 22:27:46 -05:00 committed by GitHub
parent 81e8a2641a
commit b991597342
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
5 changed files with 57 additions and 40 deletions

View file

@ -1,7 +1,7 @@
import {
LogtoOidcConfigKey,
SupportedSigningKeyAlgorithm,
type OidcConfigKeysResponse,
LogtoOidcConfigKeyType,
} from '@logto/schemas';
import { condArray } from '@silverhand/essentials';
import { useMemo, useState } from 'react';
@ -26,9 +26,10 @@ import * as styles from './index.module.scss';
function SigningKeys() {
const api = useApi();
const { t } = useTranslation(undefined, { keyPrefix: 'admin_console.tenants.signing_keys' });
const [activeTab, setActiveTab] = useState<LogtoOidcConfigKey>(LogtoOidcConfigKey.PrivateKeys);
const isPrivateKey = activeTab === LogtoOidcConfigKey.PrivateKeys;
const keyType = isPrivateKey ? 'private-keys' : 'cookie-keys';
const [keyType, setKeyType] = useState<LogtoOidcConfigKeyType>(
LogtoOidcConfigKeyType.PrivateKeys
);
const isPrivateKey = keyType === LogtoOidcConfigKeyType.PrivateKeys;
const { data, error, mutate } = useSWR<OidcConfigKeysResponse[], RequestError>(
`api/configs/oidc/${keyType}`
@ -97,17 +98,17 @@ function SigningKeys() {
<FormCard title="tenants.signing_keys.title" description="tenants.signing_keys.description">
<TabNav>
<TabNavItem
isActive={activeTab === LogtoOidcConfigKey.PrivateKeys}
isActive={keyType === LogtoOidcConfigKeyType.PrivateKeys}
onClick={() => {
setActiveTab(LogtoOidcConfigKey.PrivateKeys);
setKeyType(LogtoOidcConfigKeyType.PrivateKeys);
}}
>
<DynamicT forKey="tenants.signing_keys.type.private_key" />
</TabNavItem>
<TabNavItem
isActive={activeTab === LogtoOidcConfigKey.CookieKeys}
isActive={keyType === LogtoOidcConfigKeyType.CookieKeys}
onClick={() => {
setActiveTab(LogtoOidcConfigKey.CookieKeys);
setKeyType(LogtoOidcConfigKeyType.CookieKeys);
}}
>
<DynamicT forKey="tenants.signing_keys.type.cookie_key" />

View file

@ -11,6 +11,7 @@ import {
SupportedSigningKeyAlgorithm,
type OidcConfigKeysResponse,
type OidcConfigKey,
LogtoOidcConfigKeyType,
} from '@logto/schemas';
import { z } from 'zod';
@ -20,19 +21,11 @@ import { exportJWK } from '#src/utils/jwks.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 =>
key === LogtoOidcPrivateKeyType.PrivateKeys
const getOidcConfigKeyDatabaseColumnName = (key: LogtoOidcConfigKeyType): LogtoOidcConfigKey =>
key === LogtoOidcConfigKeyType.PrivateKeys
? LogtoOidcConfigKey.PrivateKeys
: LogtoOidcConfigKey.CookieKeys;
@ -105,7 +98,7 @@ export default function logtoConfigRoutes<T extends AuthedRouter>(
'/configs/oidc/:keyType',
koaGuard({
params: z.object({
keyType: z.nativeEnum(LogtoOidcPrivateKeyType),
keyType: z.nativeEnum(LogtoOidcConfigKeyType),
}),
response: z.array(oidcConfigKeysResponseGuard),
status: [200, 404],
@ -131,7 +124,7 @@ export default function logtoConfigRoutes<T extends AuthedRouter>(
'/configs/oidc/:keyType/:keyId',
koaGuard({
params: z.object({
keyType: z.nativeEnum(LogtoOidcPrivateKeyType),
keyType: z.nativeEnum(LogtoOidcConfigKeyType),
keyId: z.string(),
}),
status: [204, 404, 422],
@ -171,7 +164,7 @@ export default function logtoConfigRoutes<T extends AuthedRouter>(
'/configs/oidc/:keyType/rotate',
koaGuard({
params: z.object({
keyType: z.nativeEnum(LogtoOidcPrivateKeyType),
keyType: z.nativeEnum(LogtoOidcConfigKeyType),
}),
body: z.object({
signingKeyAlgorithm: z.nativeEnum(SupportedSigningKeyAlgorithm).optional(),

View file

@ -2,6 +2,7 @@ import {
SupportedSigningKeyAlgorithm,
type AdminConsoleData,
type OidcConfigKeysResponse,
type LogtoOidcConfigKeyType,
} from '@logto/schemas';
import { authedAdminApi } from './api.js';
@ -16,14 +17,14 @@ export const updateAdminConsoleConfig = async (payload: Partial<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[]>();
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}`);
export const rotateOidcKeys = async (
keyType: 'private-keys' | 'cookie-keys',
keyType: LogtoOidcConfigKeyType,
signingKeyAlgorithm: SupportedSigningKeyAlgorithm = SupportedSigningKeyAlgorithm.EC
) =>
authedAdminApi

View file

@ -1,4 +1,8 @@
import { SupportedSigningKeyAlgorithm, type AdminConsoleData } from '@logto/schemas';
import {
SupportedSigningKeyAlgorithm,
type AdminConsoleData,
LogtoOidcConfigKeyType,
} from '@logto/schemas';
import {
deleteOidcKey,
@ -33,8 +37,8 @@ describe('admin console sign-in experience', () => {
});
it('should get OIDC keys successfully', async () => {
const privateKeys = await getOidcKeys('private-keys');
const cookieKeys = await getOidcKeys('cookie-keys');
const privateKeys = await getOidcKeys(LogtoOidcConfigKeyType.PrivateKeys);
const cookieKeys = await getOidcKeys(LogtoOidcConfigKeyType.CookieKeys);
expect(privateKeys).toHaveLength(1);
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 () => {
const privateKeys = await getOidcKeys('private-keys');
const privateKeys = await getOidcKeys(LogtoOidcConfigKeyType.PrivateKeys);
expect(privateKeys).toHaveLength(1);
await expectRejects(deleteOidcKey('private-keys', privateKeys[0]!.id), {
await expectRejects(deleteOidcKey(LogtoOidcConfigKeyType.PrivateKeys, privateKeys[0]!.id), {
code: 'oidc.key_required',
statusCode: 422,
});
const cookieKeys = await getOidcKeys('cookie-keys');
const cookieKeys = await getOidcKeys(LogtoOidcConfigKeyType.CookieKeys);
expect(cookieKeys).toHaveLength(1);
await expectRejects(deleteOidcKey('cookie-keys', cookieKeys[0]!.id), {
await expectRejects(deleteOidcKey(LogtoOidcConfigKeyType.CookieKeys, cookieKeys[0]!.id), {
code: 'oidc.key_required',
statusCode: 422,
});
});
it('should rotate OIDC keys successfully', async () => {
const existingPrivateKeys = await getOidcKeys('private-keys');
const newPrivateKeys = await rotateOidcKeys('private-keys', SupportedSigningKeyAlgorithm.RSA);
const existingPrivateKeys = await getOidcKeys(LogtoOidcConfigKeyType.PrivateKeys);
const newPrivateKeys = await rotateOidcKeys(
LogtoOidcConfigKeyType.PrivateKeys,
SupportedSigningKeyAlgorithm.RSA
);
expect(newPrivateKeys).toHaveLength(2);
expect(newPrivateKeys).toMatchObject([
@ -77,8 +84,8 @@ describe('admin console sign-in experience', () => {
]);
expect(newPrivateKeys[1]?.id).toBe(existingPrivateKeys[0]?.id);
const existingCookieKeys = await getOidcKeys('cookie-keys');
const newCookieKeys = await rotateOidcKeys('cookie-keys');
const existingCookieKeys = await getOidcKeys(LogtoOidcConfigKeyType.CookieKeys);
const newCookieKeys = await rotateOidcKeys(LogtoOidcConfigKeyType.CookieKeys);
expect(newCookieKeys).toHaveLength(2);
expect(newCookieKeys).toMatchObject([
@ -91,7 +98,7 @@ describe('admin console sign-in experience', () => {
});
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).toMatchObject([
@ -101,7 +108,10 @@ describe('admin console sign-in experience', () => {
{ 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).toMatchObject([

View file

@ -1,13 +1,25 @@
import type { ZodType } 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 {
PrivateKeys = 'oidc.privateKeys',
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 {
RSA = 'RSA',
EC = 'EC',