0
Fork 0
mirror of https://github.com/logto-io/logto.git synced 2025-01-20 21:32:31 -05:00

feat(console): add api context type declarations (#6533)

* feat(console): add api context type declarations

add api context type declarations

* chore(console): update type name

update custom jwt api context type name

* feat(console): update the cutsom JWT editor

update the custom JWT editor
This commit is contained in:
simeng-li 2024-09-04 09:59:46 +08:00 committed by GitHub
parent b51680a78f
commit 31035816c4
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
22 changed files with 201 additions and 63 deletions

View file

@ -0,0 +1,24 @@
/**
* @fileoverview
*
* This file manually define some of the types that are used in the JWT customizer scripts.
*/
// eslint-disable-next-line unused-imports/no-unused-imports -- For type reference
import { CustomJwtApiContext } from '@logto/schemas';
/**
* @returns {CustomJwtApiContext}
*/
export const jwtCustomizerApiContextTypeDefinition = `type CustomJwtApiContext = {
/**
* Reject the the current token exchange request.
*
* @remarks
* This function will reject the current token exchange request and throw
* an OIDC AccessDenied error to the client.
*
* @param {string} [message] - The custom error message.
*/
denyAccess: (message?: string) => never;
};`;

View file

@ -10,6 +10,8 @@ import prettier from 'prettier';
import { type ZodTypeAny } from 'zod';
import { printNode, zodToTs } from 'zod-to-ts';
import { jwtCustomizerApiContextTypeDefinition } from './custom-jwt-customizer-type-definition.js';
const filePath = 'src/consts/jwt-customizer-type-definition.ts';
const typeIdentifiers = `export enum JwtCustomizerTypeDefinitionKey {
@ -18,6 +20,7 @@ const typeIdentifiers = `export enum JwtCustomizerTypeDefinitionKey {
AccessTokenPayload = 'AccessTokenPayload',
ClientCredentialsPayload = 'ClientCredentialsPayload',
EnvironmentVariables = 'EnvironmentVariables',
CustomJwtApiContext = 'CustomJwtApiContext',
};`;
const inferTsDefinitionFromZod = (zodSchema: ZodTypeAny, identifier: string): string => {
@ -70,6 +73,8 @@ export const jwtCustomizerGrantContextTypeDefinition = \`${jwtCustomizerGrantCon
export const accessTokenPayloadTypeDefinition = \`${accessTokenPayloadTypeDefinition}\`;
export const clientCredentialsPayloadTypeDefinition = \`${clientCredentialsPayloadTypeDefinition}\`;
export const jwtCustomizerApiContextTypeDefinition = \`${jwtCustomizerApiContextTypeDefinition}\`;
`;
const formattedFileContent = await prettier.format(fileContent, {

View file

@ -1,4 +1,5 @@
import { type JsonObject, type RequestErrorBody } from '@logto/schemas';
import { trySafe } from '@silverhand/essentials';
import { HTTPError } from 'ky';
import { useCallback, useState } from 'react';
import { useFormContext } from 'react-hook-form';
@ -35,14 +36,17 @@ const useTestHandler = () => {
.catch(async (error: unknown) => {
if (error instanceof HTTPError) {
const { response } = error;
const metadata = await response.clone().json<RequestErrorBody>();
const errorResponse = await response.clone().json<RequestErrorBody>();
// Get error message from cloud connection client.
if (metadata.code === jwtCustomizerGeneralErrorCode) {
const result = z.object({ message: z.string() }).safeParse(metadata.data);
if (errorResponse.code === jwtCustomizerGeneralErrorCode) {
const result = z
.object({ message: z.string(), code: z.string().optional() })
.safeParse(errorResponse.data);
if (result.success) {
setTestResult({
error: result.data.message,
error: JSON.stringify(result.data, null, 2),
});
return;
}
@ -54,8 +58,8 @@ const useTestHandler = () => {
* 1. `RequestError`
* 2. `koaGuard`
*/
if (metadata.code === apiInvalidInputErrorCode) {
const result = z.string().safeParse(metadata.details);
if (errorResponse.code === apiInvalidInputErrorCode) {
const result = z.string().safeParse(errorResponse.details);
if (result.success) {
setTestResult({
error: result.data,
@ -63,6 +67,15 @@ const useTestHandler = () => {
return;
}
}
setTestResult({
error: trySafe(
() => JSON.stringify(errorResponse, null, 2),
() => errorResponse.message
),
});
return;
}
setTestResult({

View file

@ -13,6 +13,7 @@ export enum CardType {
TokenData = 'token_data',
FetchExternalData = 'fetch_external_data',
EnvironmentVariables = 'environment_variables',
ApiContext = 'api_context',
}
type GuardCardProps = {

View file

@ -5,8 +5,10 @@ import { useState } from 'react';
import { useFormContext } from 'react-hook-form';
import { useTranslation } from 'react-i18next';
import { isDevFeaturesEnabled } from '@/consts/env';
import { type JwtCustomizerForm } from '@/pages/CustomizeJwtDetails/type';
import {
denyAccessCodeExample,
environmentVariablesCodeExample,
fetchExternalDataCodeExample,
sampleCodeEditorOptions,
@ -136,6 +138,24 @@ function InstructionTab({ isActive }: Props) {
options={sampleCodeEditorOptions}
/>
</GuideCard>
{isDevFeaturesEnabled && (
<GuideCard
name={CardType.ApiContext}
isExpanded={expendCard === CardType.ApiContext}
setExpanded={(expand) => {
setExpendCard(expand ? CardType.ApiContext : undefined);
}}
>
<Editor
language="typescript"
className={styles.sampleCode}
value={denyAccessCodeExample}
height="320px"
theme="logto-dark"
options={sampleCodeEditorOptions}
/>
</GuideCard>
)}
<div className={tabContentStyles.description}>{t('jwt_claims.jwt_claims_description')}</div>
</div>
);

View file

@ -9,6 +9,7 @@ import { type EditorProps } from '@monaco-editor/react';
import TokenFileIcon from '@/assets/icons/token-file-icon.svg?react';
import UserFileIcon from '@/assets/icons/user-file-icon.svg?react';
import { isDevFeaturesEnabled } from '@/consts/env.js';
import type { ModelSettings } from '../MainContent/MonacoCodeEditor/type.js';
@ -19,59 +20,114 @@ import {
} from './type-definitions.js';
/**
* JWT token code editor configuration
* Define the user access token JwtCustomizer payload type definitions
*
* @see {CustomJwtScriptPayload}
*/
const accessTokenJwtCustomizerDefinition = `
declare interface CustomJwtClaims extends Record<string, any> {}
/** Logto internal data that can be used to pass additional information
* @param {${JwtCustomizerTypeDefinitionKey.JwtCustomizerUserContext}} user - The user info associated with the token.
*
* @param {${
JwtCustomizerTypeDefinitionKey.JwtCustomizerUserContext
}} user - The user info associated with the token.
*/
declare type Context = {
/**
* The user data associated with the token.
*/
user: ${JwtCustomizerTypeDefinitionKey.JwtCustomizerUserContext};
/**
* The grant context associated with the token.
*/
grant?: ${JwtCustomizerTypeDefinitionKey.JwtCustomizerGrantContext};
}
declare type Payload = {
/**
* Token payload.
*/
token: ${JwtCustomizerTypeDefinitionKey.AccessTokenPayload};
/**
* Logto internal data that can be used to pass additional information.
*
* @params {${JwtCustomizerTypeDefinitionKey.JwtCustomizerUserContext}} user
* @params {${JwtCustomizerTypeDefinitionKey.JwtCustomizerGrantContext}} [grant]
*/
context: Context;
/**
* Custom environment variables.
*/
environmentVariables: ${JwtCustomizerTypeDefinitionKey.EnvironmentVariables};
${
isDevFeaturesEnabled
? `
/**
* Logto API context, provides callback methods for access control.
*
* @param {${JwtCustomizerTypeDefinitionKey.CustomJwtApiContext}} api
*/
api: ${JwtCustomizerTypeDefinitionKey.CustomJwtApiContext};`
: ''
}
};`;
/**
* Define the client credentials token JwtCustomizer payload type definitions
*
* @see {CustomJwtScriptPayload}
*/
const clientCredentialsJwtCustomizerDefinition = `
declare interface CustomJwtClaims extends Record<string, any> {}
declare type Payload = {
/**
* Token payload.
*/
token: ${JwtCustomizerTypeDefinitionKey.AccessTokenPayload};
/**
* Custom environment variables.
*/
environmentVariables: ${JwtCustomizerTypeDefinitionKey.EnvironmentVariables};
${
isDevFeaturesEnabled
? `
/**
* Logto API context, callback methods for access control.
*
* @param {${JwtCustomizerTypeDefinitionKey.CustomJwtApiContext}} api
*/
api: ${JwtCustomizerTypeDefinitionKey.CustomJwtApiContext};`
: ''
}
};`;
export const defaultAccessTokenJwtCustomizerCode = `/**
* This function is called during the access token generation process to get custom claims for the JWT token.
* Limit custom claims to under 50KB.
*
* @param {Object} payload - The input payload of the function.
* @param {${JwtCustomizerTypeDefinitionKey.AccessTokenPayload}} payload.token -The JWT token.
* @param {Context} payload.context - Logto internal data that can be used to pass additional information
* @param {${JwtCustomizerTypeDefinitionKey.EnvironmentVariables}} [payload.environmentVariables] - The environment variables.
*
* @returns The custom claims.
*/
const getCustomJwtClaims = async ({ token, context, environmentVariables }) => {
* This function is called during the access token generation process to get custom claims for the JWT token.
* Limit custom claims to under 50KB.
*
* @param {Payload} payload - The input argument of the function.
*
* @returns The custom claims.
*/
const getCustomJwtClaims = async ({ token, context, environmentVariables${
isDevFeaturesEnabled ? ', api' : ''
} }) => {
return {};
}`;
export const defaultClientCredentialsJwtCustomizerCode = `/**
* This function is called during the access token generation process to get custom claims for the JWT token.
* Limit custom claims to under 50KB.
*
* @param {Object} payload - The input payload of the function.
* @param {${JwtCustomizerTypeDefinitionKey.ClientCredentialsPayload}} payload.token -The JWT token.
* @param {${JwtCustomizerTypeDefinitionKey.EnvironmentVariables}} [payload.environmentVariables] - The environment variables.
*
* @returns The custom claims.
*/
const getCustomJwtClaims = async ({ token, environmentVariables }) => {
* This function is called during the access token generation process to get custom claims for the JWT token.
* Limit custom claims to under 50KB.
*
* @param {Payload} payload - The input payload of the function.
*
* @returns The custom claims.
*/
const getCustomJwtClaims = async ({ token, environmentVariables${
isDevFeaturesEnabled ? ', api' : ''
} }) => {
return {};
}`;
@ -160,6 +216,15 @@ export const environmentVariablesCodeExample = `const getCustomJwtClaimsSample =
};
};`;
export const denyAccessCodeExample = `/**
* @param {Payload} payload - The input payload of the function.
*/
getCustomJwtClaims = async ({ api }) => {
// Conditionally deny access
api.denyAccess('Access denied');
return {};
};`;
/**
* Tester Code Editor configs
*/

View file

@ -4,6 +4,7 @@ import {
clientCredentialsPayloadTypeDefinition,
jwtCustomizerUserContextTypeDefinition,
jwtCustomizerGrantContextTypeDefinition,
jwtCustomizerApiContextTypeDefinition,
} from '@/consts/jwt-customizer-type-definition';
import { type JwtCustomizerForm } from '../type';
@ -21,11 +22,15 @@ export const buildAccessTokenJwtCustomizerContextTsDefinition = () => {
declare ${jwtCustomizerGrantContextTypeDefinition}
declare ${jwtCustomizerApiContextTypeDefinition}
declare ${accessTokenPayloadTypeDefinition}`;
};
export const buildClientCredentialsJwtCustomizerContextTsDefinition = () =>
`declare ${clientCredentialsPayloadTypeDefinition}`;
`declare ${clientCredentialsPayloadTypeDefinition}
declare ${jwtCustomizerApiContextTypeDefinition}`;
export const buildEnvironmentVariablesTypeDefinition = (
envVariables?: JwtCustomizerForm['environmentVariables']

View file

@ -28,12 +28,12 @@ const jwt_claims = {
jwt_claims_description: 'المطالبات الافتراضية مضمنة تلقائيًا في JWT ولا يمكن استبدالها.',
user_data: {
title: 'بيانات المستخدم',
subtitle: 'استخدم معلمة الإدخال `data.user` لتوفير معلومات المستخدم الحيوية.',
subtitle: 'استخدم معلمة الإدخال `context.user` لتوفير معلومات المستخدم الحيوية.',
},
grant_data: {
title: 'بيانات المنحة',
subtitle:
'استخدم معلمة الإدخال `data.grant` لتوفير معلومات المنحة الحيوية، متاحة فقط لتبادل الرموز.',
'استخدم معلمة الإدخال `context.grant` لتوفير معلومات المنحة الحيوية، متاحة فقط لتبادل الرموز.',
},
token_data: {
title: 'بيانات الرمز',

View file

@ -33,12 +33,12 @@ const jwt_claims = {
user_data: {
title: 'Benutzerdaten',
subtitle:
'Verwenden Sie den `data.user` Eingabeparameter, um wichtige Benutzerinformationen bereitzustellen.',
'Verwenden Sie den `context.user` Eingabeparameter, um wichtige Benutzerinformationen bereitzustellen.',
},
grant_data: {
title: 'Zugriffsdaten',
subtitle:
'Verwenden Sie den `data.grant` Eingabeparameter, um wichtige Informationen zu gewähren, nur für den Token-Austausch verfügbar.',
'Verwenden Sie den `context.grant` Eingabeparameter, um wichtige Informationen zu gewähren, nur für den Token-Austausch verfügbar.',
},
token_data: {
title: 'Token-Daten',

View file

@ -27,18 +27,22 @@ const jwt_claims = {
test_tab: 'Test context',
jwt_claims_description: 'Default claims are auto-included in the JWT and cannot be overridden.',
user_data: {
title: 'User data',
subtitle: 'Use `data.user` input parameter to provide vital user info.',
title: 'User context',
subtitle: 'Use `context.user` input parameter to provide vital user info.',
},
grant_data: {
title: 'Grant data',
title: 'Grant context',
subtitle:
'Use `data.grant` input parameter to provide vital grant info, only available for token exchange.',
'Use `context.grant` input parameter to provide vital grant info, only available for token exchange.',
},
token_data: {
title: 'Token data',
title: 'Token payload',
subtitle: 'Use `token` input parameter for current access token payload. ',
},
api_context: {
title: 'API context',
subtitle: 'Use `api.denyAccess` method to reject the current token exchange request.',
},
fetch_external_data: {
title: 'Fetch external data',
subtitle: 'Incorporate data from your external APIs directly into claims.',

View file

@ -31,12 +31,12 @@ const jwt_claims = {
user_data: {
title: 'Datos del usuario',
subtitle:
'Utilice el parámetro de entrada `data.user` para proporcionar información vital del usuario.',
'Utilice el parámetro de entrada `context.user` para proporcionar información vital del usuario.',
},
grant_data: {
title: 'Datos de concesión',
subtitle:
'Use el parámetro de entrada `data.grant` para proporcionar información vital de la concesión, solo disponible para el intercambio de tokens.',
'Use el parámetro de entrada `context.grant` para proporcionar información vital de la concesión, solo disponible para el intercambio de tokens.',
},
token_data: {
title: 'Datos del token',

View file

@ -32,12 +32,12 @@ const jwt_claims = {
user_data: {
title: 'Données utilisateur',
subtitle:
"Utilisez le paramètre d'entrée `data.user` pour fournir des informations vitales sur l'utilisateur.",
"Utilisez le paramètre d'entrée `context.user` pour fournir des informations vitales sur l'utilisateur.",
},
grant_data: {
title: 'Données de subvention',
subtitle:
'Utilisez le paramètre dentrée `data.grant` pour fournir des informations cruciales sur les subventions, uniquement disponibles pour léchange de jetons.',
'Utilisez le paramètre dentrée `context.grant` pour fournir des informations cruciales sur les subventions, uniquement disponibles pour léchange de jetons.',
},
token_data: {
title: 'Données du jeton',

View file

@ -31,12 +31,12 @@ const jwt_claims = {
user_data: {
title: 'Dati utente',
subtitle:
"Utilizza il parametro di input `data.user` per fornire informazioni vitali sull'utente.",
"Utilizza il parametro di input `context.user` per fornire informazioni vitali sull'utente.",
},
grant_data: {
title: 'Dati concessione',
subtitle:
'Usa il parametro di input `data.grant` per fornire informazioni vitali sulla concessione, disponibile solo per lo scambio di token.',
'Usa il parametro di input `context.grant` per fornire informazioni vitali sulla concessione, disponibile solo per lo scambio di token.',
},
token_data: {
title: 'Dati token',

View file

@ -28,12 +28,12 @@ const jwt_claims = {
jwt_claims_description: 'デフォルトクレームはJWTに自動的に含まれ、オーバーライドできません。',
user_data: {
title: 'ユーザーデータ',
subtitle: '`data.user`入力パラメータを使用して重要なユーザー情報を提供します。',
subtitle: '`context.user`入力パラメータを使用して重要なユーザー情報を提供します。',
},
grant_data: {
title: 'グラントデータ',
subtitle:
'`data.grant`入力パラメータを使用して重要なグラント情報を提供します。これはトークン交換のためにのみ使用できます。',
'`context.grant`入力パラメータを使用して重要なグラント情報を提供します。これはトークン交換のためにのみ使用できます。',
},
token_data: {
title: 'トークンデータ',

View file

@ -28,12 +28,12 @@ const jwt_claims = {
jwt_claims_description: '기본 클레임은 JWT에 자동으로 추가되며 재정의할 수 없습니다.',
user_data: {
title: '사용자 데이터',
subtitle: '`data.user` 입력 매개변수를 사용하여 중요한 사용자 정보 제공.',
subtitle: '`context.user` 입력 매개변수를 사용하여 중요한 사용자 정보 제공.',
},
grant_data: {
title: 'Grant 데이터',
subtitle:
'`data.grant` 입력 매개변수를 사용하여 중요한 Grant 정보를 제공하고, 이 정보는 오직 토큰 교환에만 사용할 수 있습니다.',
'`context.grant` 입력 매개변수를 사용하여 중요한 Grant 정보를 제공하고, 이 정보는 오직 토큰 교환에만 사용할 수 있습니다.',
},
token_data: {
title: '토큰 데이터',

View file

@ -31,12 +31,12 @@ const jwt_claims = {
user_data: {
title: 'Dane użytkownika',
subtitle:
'Użyj parametru wejściowego `data.user`, aby dostarczyć istotne informacje o użytkowniku.',
'Użyj parametru wejściowego `context.user`, aby dostarczyć istotne informacje o użytkowniku.',
},
grant_data: {
title: 'Dane przyznania',
subtitle:
'Użyj parametru wejściowego `data.grant`, aby dostarczyć istotne informacje dotyczące przyznania, dostępne tylko przy wymianie tokenu.',
'Użyj parametru wejściowego `context.grant`, aby dostarczyć istotne informacje dotyczące przyznania, dostępne tylko przy wymianie tokenu.',
},
token_data: {
title: 'Dane tokenu',

View file

@ -29,12 +29,13 @@ const jwt_claims = {
'As reivindicações padrão são automaticamente incluídas no JWT e não podem ser substituídas.',
user_data: {
title: 'Dados do usuário',
subtitle: 'Use o parâmetro de entrada `data.user` para fornecer informações vitais do usuário.',
subtitle:
'Use o parâmetro de entrada `context.user` para fornecer informações vitais do usuário.',
},
grant_data: {
title: 'Dados da concessão',
subtitle:
'Use o parâmetro de entrada `data.grant` para fornecer informações vitais da concessão, disponível apenas para troca de token.',
'Use o parâmetro de entrada `context.grant` para fornecer informações vitais da concessão, disponível apenas para troca de token.',
},
token_data: {
title: 'Dados do token',

View file

@ -31,12 +31,12 @@ const jwt_claims = {
user_data: {
title: 'Dados do utilizador',
subtitle:
'Utilize o parâmetro de entrada `data.user` para fornecer informações vitais do utilizador.',
'Utilize o parâmetro de entrada `context.user` para fornecer informações vitais do utilizador.',
},
grant_data: {
title: 'Dados da concessão',
subtitle:
'Utilize o parâmetro de entrada `data.grant` para fornecer informações vitais da concessão, disponíveis apenas para troca de token.',
'Utilize o parâmetro de entrada `context.grant` para fornecer informações vitais da concessão, disponíveis apenas para troca de token.',
},
token_data: {
title: 'Dados do token',

View file

@ -31,12 +31,12 @@ const jwt_claims = {
user_data: {
title: 'Данные пользователя',
subtitle:
'Используйте параметр ввода `data.user` для предоставления важной информации о пользователе.',
'Используйте параметр ввода `context.user` для предоставления важной информации о пользователе.',
},
grant_data: {
title: 'Данные предоставления',
subtitle:
'Используйте параметр ввода `data.grant` для предоставления важной информации о предоставлении, доступной только для обмена токенами.',
'Используйте параметр ввода `context.grant` для предоставления важной информации о предоставлении, доступной только для обмена токенами.',
},
token_data: {
title: 'Данные токена',

View file

@ -27,11 +27,11 @@ const jwt_claims = {
jwt_claims_description: '默认声明会自动包含在JWT中不能被覆盖。',
user_data: {
title: '用户数据',
subtitle: '使用`data.user`输入参数提供重要用户信息。',
subtitle: '使用`context.user`输入参数提供重要用户信息。',
},
grant_data: {
title: '授权数据',
subtitle: '使用`data.grant`输入参数提供重要的授权信息,仅适用于令牌交换。',
subtitle: '使用`context.grant`输入参数提供重要的授权信息,仅适用于令牌交换。',
},
token_data: {
title: '令牌数据',

View file

@ -27,11 +27,11 @@ const jwt_claims = {
jwt_claims_description: '默認声明會自動包含在 JWT 中,無法覆蓋。',
user_data: {
title: '用戶數據',
subtitle: '使用 `data.user` 輸入參數提供重要用戶信息。',
subtitle: '使用 `context.user` 輸入參數提供重要用戶信息。',
},
grant_data: {
title: '授權數據',
subtitle: '使用 `data.grant` 輸入參數提供重要的授權信息,只適用於權杖交換。',
subtitle: '使用 `context.grant` 輸入參數提供重要的授權信息,只適用於權杖交換。',
},
token_data: {
title: '權杖數據',

View file

@ -27,11 +27,11 @@ const jwt_claims = {
jwt_claims_description: '默認声明自動包含在 JWT 中,無法覆蓋。',
user_data: {
title: '用戶數據',
subtitle: '使用 `data.user` 輸入參數提供重要用戶信息。',
subtitle: '使用 `context.user` 輸入參數提供重要用戶信息。',
},
grant_data: {
title: '授權資料',
subtitle: '使用 `data.grant` 輸入參數提供重要授權信息,僅適用於令牌交換。',
subtitle: '使用 `context.grant` 輸入參數提供重要授權信息,僅適用於令牌交換。',
},
token_data: {
title: '令牌數據',