diff --git a/packages/console/src/pages/JwtClaims/RightPanel/EnvironmentVariablesField.tsx b/packages/console/src/pages/JwtClaims/RightPanel/EnvironmentVariablesField.tsx new file mode 100644 index 000000000000..38ef49ed6c5a --- /dev/null +++ b/packages/console/src/pages/JwtClaims/RightPanel/EnvironmentVariablesField.tsx @@ -0,0 +1,114 @@ +import { useCallback, useMemo } from 'react'; +import { useFieldArray, useFormContext } from 'react-hook-form'; +import { useTranslation } from 'react-i18next'; + +import FormField from '@/ds-components/FormField'; +import KeyValueInputField from '@/ds-components/KeyValueInputField'; + +import { type JwtClaimsFormType } from '../type'; + +const isValidKey = (key: string) => { + return /^\w+$/.test(key); +}; + +function EnvironmentVariablesField() { + const { t } = useTranslation(undefined, { keyPrefix: 'admin_console' }); + + const { + register, + getValues, + trigger, + formState: { + errors: { environmentVariables: envVariableErrors }, + submitCount, + }, + } = useFormContext(); + + // Read the form controller from the context @see {@link https://react-hook-form.com/docs/usefieldarray} + const { fields, remove, append } = useFieldArray({ + name: 'environmentVariables', + }); + + const keyValidator = useCallback( + (key: string, index: number) => { + const envVariables = getValues('environmentVariables'); + + if (!envVariables) { + return true; + } + + // Unique key validation + if (envVariables.filter(({ key: _key }) => _key.length > 0 && _key === key).length > 1) { + // Reuse the same error phrase key from webhook settings + return t('webhook_details.settings.key_duplicated_error'); + } + + // Empty key validation (if value is present) + const correspondValue = getValues(`environmentVariables.${index}.value`); + if (correspondValue) { + // Reuse the same error phrase key from webhook settings + return Boolean(key) || t('webhook_details.settings.key_missing_error'); + } + + // Key format validation + if (Boolean(key) && !isValidKey(key)) { + // Reuse the same error phrase key from webhook settings + return t('webhook_details.settings.invalid_key_error'); + } + + return true; + }, + [getValues, t] + ); + + const valueValidator = useCallback( + (value: string, index: number) => { + return getValues(`environmentVariables.${index}.value`) + ? Boolean(value) || t('webhook_details.settings.value_missing_error') + : true; + }, + [getValues, t] + ); + + const revalidate = useCallback(() => { + for (const [index] of fields.entries()) { + void trigger(`environmentVariables.${index}.key`); + + // Only trigger value validation if the form has been submitted + if (submitCount > 0) { + void trigger(`environmentVariables.${index}.value`); + } + } + }, [fields, submitCount, trigger]); + + const getInputFieldProps = useMemo( + () => ({ + key: (index: number) => + register(`environmentVariables.${index}.key`, { + validate: (key) => keyValidator(key, index), + onChange: revalidate, + }), + value: (index: number) => + register(`environmentVariables.${index}.value`, { + validate: (value) => valueValidator(value, index), + onChange: revalidate, + }), + }), + [register, revalidate, keyValidator, valueValidator] + ); + + return ( + + error)} + getInputFieldProps={getInputFieldProps} + onAppend={append} + onRemove={remove} + /> + + ); +} + +export default EnvironmentVariablesField; diff --git a/packages/console/src/pages/JwtClaims/RightPanel/index.tsx b/packages/console/src/pages/JwtClaims/RightPanel/index.tsx index 18a81b5293da..4dbf92802f71 100644 --- a/packages/console/src/pages/JwtClaims/RightPanel/index.tsx +++ b/packages/console/src/pages/JwtClaims/RightPanel/index.tsx @@ -16,6 +16,7 @@ import { fetchExternalDataCodeExample, } from '../config'; +import EnvironmentVariablesField from './EnvironmentVariablesField'; import GuideCard, { CardType } from './GuideCard'; import * as styles from './index.module.scss'; @@ -107,7 +108,19 @@ function RightPanel({ tokenType }: Props) { options={fetchExternalDataEditorOptions} /> - + + {/** + * We use useFieldArray hook to manage the list of environment variables in the EnvironmentVariablesField component. + * useFieldArray will read the form context and return the necessary methods and values to manage the list. + * The form context will mutate when the tokenType changes. It will provide different form state and methods based on the tokenType. (@see JwtClaims component.) + * However, the form context/controller updates did not trigger a re-render of the useFieldArray hook. (@see {@link https://github.com/react-hook-form/react-hook-form/blob/master/src/useFieldArray.ts#L95}) + * + * This cause issues when the tokenType changes and the environment variables list is not rerendered. The form state will be stale. + * In order to fix this, we need to re-render the EnvironmentVariablesField component when the tokenType changes. + * Achieve this by adding a key to the EnvironmentVariablesField component. Force a re-render when the tokenType changes. + */} + + ); diff --git a/packages/console/src/pages/JwtClaims/index.tsx b/packages/console/src/pages/JwtClaims/index.tsx index d6e488214af4..b2eefa8043ce 100644 --- a/packages/console/src/pages/JwtClaims/index.tsx +++ b/packages/console/src/pages/JwtClaims/index.tsx @@ -2,6 +2,7 @@ import { withAppInsights } from '@logto/app-insights/react/AppInsightsReact'; import classNames from 'classnames'; import type { TFuncKey } from 'i18next'; import { useMemo } from 'react'; +import { useForm, FormProvider } from 'react-hook-form'; import { useTranslation } from 'react-i18next'; import Card from '@/ds-components/Card'; @@ -13,6 +14,7 @@ import { type Model } from './MonacoCodeEditor/type'; import RightPanel from './RightPanel'; import { userJwtFile, machineToMachineJwtFile, JwtTokenType } from './config'; import * as styles from './index.module.scss'; +import { type JwtClaimsFormType } from './type'; export { JwtTokenType } from './config'; @@ -39,6 +41,18 @@ const getPath = (tab: JwtTokenType) => `/jwt-claims/${tab}`; function JwtClaims({ tab }: Props) { const { t } = useTranslation(undefined, { keyPrefix: 'admin_console' }); + const userJwtClaimsForm = useForm({ + defaultValues: { + environmentVariables: [{ key: '', value: '' }], + }, + }); + + const machineToMachineJwtClaimsForm = useForm({ + defaultValues: { + environmentVariables: [{ key: '', value: '' }], + }, + }); + // TODO: API integration, read/write the custom claims code value const activeModel = useMemo(() => { return tab === JwtTokenType.UserAccessToken ? userJwtFile : machineToMachineJwtFile; @@ -58,17 +72,23 @@ function JwtClaims({ tab }: Props) { ))} -
- -
- {t('jwt_claims.code_editor_title', { - token: t(`jwt_claims.${phrases.token[tab]}`), - })} -
- -
- - + +
+ +
+ {t('jwt_claims.code_editor_title', { + token: t(`jwt_claims.${phrases.token[tab]}`), + })} +
+ +
+ + +
); } diff --git a/packages/console/src/pages/JwtClaims/type.ts b/packages/console/src/pages/JwtClaims/type.ts new file mode 100644 index 000000000000..cea2f1f60f06 --- /dev/null +++ b/packages/console/src/pages/JwtClaims/type.ts @@ -0,0 +1,6 @@ +export type JwtClaimsFormType = { + script?: string; + environmentVariables?: Array<{ key: string; value: string }>; + contextSample?: string; + tokenSample?: string; +}; diff --git a/packages/phrases/src/locales/de/translation/admin-console/jwt-claims.ts b/packages/phrases/src/locales/de/translation/admin-console/jwt-claims.ts index 771845bd0efe..a2d579d72d18 100644 --- a/packages/phrases/src/locales/de/translation/admin-console/jwt-claims.ts +++ b/packages/phrases/src/locales/de/translation/admin-console/jwt-claims.ts @@ -50,6 +50,8 @@ const jwt_claims = { /** UNTRANSLATED */ subtitle: 'Use environment variables to store sensitive information and access them in your custom claims handler.', + /** UNTRANSLATED */ + input_field_title: 'Add environment variables', }, /** UNTRANSLATED */ jwt_claims_hint: diff --git a/packages/phrases/src/locales/en/translation/admin-console/jwt-claims.ts b/packages/phrases/src/locales/en/translation/admin-console/jwt-claims.ts index 3fe09970437e..052e2ebd0404 100644 --- a/packages/phrases/src/locales/en/translation/admin-console/jwt-claims.ts +++ b/packages/phrases/src/locales/en/translation/admin-console/jwt-claims.ts @@ -31,6 +31,7 @@ const jwt_claims = { title: 'Set environment variables', subtitle: 'Use environment variables to store sensitive information and access them in your custom claims handler.', + input_field_title: 'Add environment variables', }, jwt_claims_hint: 'Limit custom claims to under 50KB. Default JWT claims are automatically included in the token and can not be overridden.', diff --git a/packages/phrases/src/locales/es/translation/admin-console/jwt-claims.ts b/packages/phrases/src/locales/es/translation/admin-console/jwt-claims.ts index 771845bd0efe..a2d579d72d18 100644 --- a/packages/phrases/src/locales/es/translation/admin-console/jwt-claims.ts +++ b/packages/phrases/src/locales/es/translation/admin-console/jwt-claims.ts @@ -50,6 +50,8 @@ const jwt_claims = { /** UNTRANSLATED */ subtitle: 'Use environment variables to store sensitive information and access them in your custom claims handler.', + /** UNTRANSLATED */ + input_field_title: 'Add environment variables', }, /** UNTRANSLATED */ jwt_claims_hint: diff --git a/packages/phrases/src/locales/fr/translation/admin-console/jwt-claims.ts b/packages/phrases/src/locales/fr/translation/admin-console/jwt-claims.ts index 771845bd0efe..a2d579d72d18 100644 --- a/packages/phrases/src/locales/fr/translation/admin-console/jwt-claims.ts +++ b/packages/phrases/src/locales/fr/translation/admin-console/jwt-claims.ts @@ -50,6 +50,8 @@ const jwt_claims = { /** UNTRANSLATED */ subtitle: 'Use environment variables to store sensitive information and access them in your custom claims handler.', + /** UNTRANSLATED */ + input_field_title: 'Add environment variables', }, /** UNTRANSLATED */ jwt_claims_hint: diff --git a/packages/phrases/src/locales/it/translation/admin-console/jwt-claims.ts b/packages/phrases/src/locales/it/translation/admin-console/jwt-claims.ts index 771845bd0efe..a2d579d72d18 100644 --- a/packages/phrases/src/locales/it/translation/admin-console/jwt-claims.ts +++ b/packages/phrases/src/locales/it/translation/admin-console/jwt-claims.ts @@ -50,6 +50,8 @@ const jwt_claims = { /** UNTRANSLATED */ subtitle: 'Use environment variables to store sensitive information and access them in your custom claims handler.', + /** UNTRANSLATED */ + input_field_title: 'Add environment variables', }, /** UNTRANSLATED */ jwt_claims_hint: diff --git a/packages/phrases/src/locales/ja/translation/admin-console/jwt-claims.ts b/packages/phrases/src/locales/ja/translation/admin-console/jwt-claims.ts index 771845bd0efe..a2d579d72d18 100644 --- a/packages/phrases/src/locales/ja/translation/admin-console/jwt-claims.ts +++ b/packages/phrases/src/locales/ja/translation/admin-console/jwt-claims.ts @@ -50,6 +50,8 @@ const jwt_claims = { /** UNTRANSLATED */ subtitle: 'Use environment variables to store sensitive information and access them in your custom claims handler.', + /** UNTRANSLATED */ + input_field_title: 'Add environment variables', }, /** UNTRANSLATED */ jwt_claims_hint: diff --git a/packages/phrases/src/locales/ko/translation/admin-console/jwt-claims.ts b/packages/phrases/src/locales/ko/translation/admin-console/jwt-claims.ts index 771845bd0efe..a2d579d72d18 100644 --- a/packages/phrases/src/locales/ko/translation/admin-console/jwt-claims.ts +++ b/packages/phrases/src/locales/ko/translation/admin-console/jwt-claims.ts @@ -50,6 +50,8 @@ const jwt_claims = { /** UNTRANSLATED */ subtitle: 'Use environment variables to store sensitive information and access them in your custom claims handler.', + /** UNTRANSLATED */ + input_field_title: 'Add environment variables', }, /** UNTRANSLATED */ jwt_claims_hint: diff --git a/packages/phrases/src/locales/pl-pl/translation/admin-console/jwt-claims.ts b/packages/phrases/src/locales/pl-pl/translation/admin-console/jwt-claims.ts index 771845bd0efe..a2d579d72d18 100644 --- a/packages/phrases/src/locales/pl-pl/translation/admin-console/jwt-claims.ts +++ b/packages/phrases/src/locales/pl-pl/translation/admin-console/jwt-claims.ts @@ -50,6 +50,8 @@ const jwt_claims = { /** UNTRANSLATED */ subtitle: 'Use environment variables to store sensitive information and access them in your custom claims handler.', + /** UNTRANSLATED */ + input_field_title: 'Add environment variables', }, /** UNTRANSLATED */ jwt_claims_hint: diff --git a/packages/phrases/src/locales/pt-br/translation/admin-console/jwt-claims.ts b/packages/phrases/src/locales/pt-br/translation/admin-console/jwt-claims.ts index 771845bd0efe..a2d579d72d18 100644 --- a/packages/phrases/src/locales/pt-br/translation/admin-console/jwt-claims.ts +++ b/packages/phrases/src/locales/pt-br/translation/admin-console/jwt-claims.ts @@ -50,6 +50,8 @@ const jwt_claims = { /** UNTRANSLATED */ subtitle: 'Use environment variables to store sensitive information and access them in your custom claims handler.', + /** UNTRANSLATED */ + input_field_title: 'Add environment variables', }, /** UNTRANSLATED */ jwt_claims_hint: diff --git a/packages/phrases/src/locales/pt-pt/translation/admin-console/jwt-claims.ts b/packages/phrases/src/locales/pt-pt/translation/admin-console/jwt-claims.ts index 771845bd0efe..a2d579d72d18 100644 --- a/packages/phrases/src/locales/pt-pt/translation/admin-console/jwt-claims.ts +++ b/packages/phrases/src/locales/pt-pt/translation/admin-console/jwt-claims.ts @@ -50,6 +50,8 @@ const jwt_claims = { /** UNTRANSLATED */ subtitle: 'Use environment variables to store sensitive information and access them in your custom claims handler.', + /** UNTRANSLATED */ + input_field_title: 'Add environment variables', }, /** UNTRANSLATED */ jwt_claims_hint: diff --git a/packages/phrases/src/locales/ru/translation/admin-console/jwt-claims.ts b/packages/phrases/src/locales/ru/translation/admin-console/jwt-claims.ts index 771845bd0efe..a2d579d72d18 100644 --- a/packages/phrases/src/locales/ru/translation/admin-console/jwt-claims.ts +++ b/packages/phrases/src/locales/ru/translation/admin-console/jwt-claims.ts @@ -50,6 +50,8 @@ const jwt_claims = { /** UNTRANSLATED */ subtitle: 'Use environment variables to store sensitive information and access them in your custom claims handler.', + /** UNTRANSLATED */ + input_field_title: 'Add environment variables', }, /** UNTRANSLATED */ jwt_claims_hint: diff --git a/packages/phrases/src/locales/tr-tr/translation/admin-console/jwt-claims.ts b/packages/phrases/src/locales/tr-tr/translation/admin-console/jwt-claims.ts index 771845bd0efe..a2d579d72d18 100644 --- a/packages/phrases/src/locales/tr-tr/translation/admin-console/jwt-claims.ts +++ b/packages/phrases/src/locales/tr-tr/translation/admin-console/jwt-claims.ts @@ -50,6 +50,8 @@ const jwt_claims = { /** UNTRANSLATED */ subtitle: 'Use environment variables to store sensitive information and access them in your custom claims handler.', + /** UNTRANSLATED */ + input_field_title: 'Add environment variables', }, /** UNTRANSLATED */ jwt_claims_hint: diff --git a/packages/phrases/src/locales/zh-cn/translation/admin-console/jwt-claims.ts b/packages/phrases/src/locales/zh-cn/translation/admin-console/jwt-claims.ts index 771845bd0efe..a2d579d72d18 100644 --- a/packages/phrases/src/locales/zh-cn/translation/admin-console/jwt-claims.ts +++ b/packages/phrases/src/locales/zh-cn/translation/admin-console/jwt-claims.ts @@ -50,6 +50,8 @@ const jwt_claims = { /** UNTRANSLATED */ subtitle: 'Use environment variables to store sensitive information and access them in your custom claims handler.', + /** UNTRANSLATED */ + input_field_title: 'Add environment variables', }, /** UNTRANSLATED */ jwt_claims_hint: diff --git a/packages/phrases/src/locales/zh-hk/translation/admin-console/jwt-claims.ts b/packages/phrases/src/locales/zh-hk/translation/admin-console/jwt-claims.ts index 771845bd0efe..a2d579d72d18 100644 --- a/packages/phrases/src/locales/zh-hk/translation/admin-console/jwt-claims.ts +++ b/packages/phrases/src/locales/zh-hk/translation/admin-console/jwt-claims.ts @@ -50,6 +50,8 @@ const jwt_claims = { /** UNTRANSLATED */ subtitle: 'Use environment variables to store sensitive information and access them in your custom claims handler.', + /** UNTRANSLATED */ + input_field_title: 'Add environment variables', }, /** UNTRANSLATED */ jwt_claims_hint: diff --git a/packages/phrases/src/locales/zh-tw/translation/admin-console/jwt-claims.ts b/packages/phrases/src/locales/zh-tw/translation/admin-console/jwt-claims.ts index 771845bd0efe..a2d579d72d18 100644 --- a/packages/phrases/src/locales/zh-tw/translation/admin-console/jwt-claims.ts +++ b/packages/phrases/src/locales/zh-tw/translation/admin-console/jwt-claims.ts @@ -50,6 +50,8 @@ const jwt_claims = { /** UNTRANSLATED */ subtitle: 'Use environment variables to store sensitive information and access them in your custom claims handler.', + /** UNTRANSLATED */ + input_field_title: 'Add environment variables', }, /** UNTRANSLATED */ jwt_claims_hint: