Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat(console): integrate jwt customizer test api #5532

Merged
merged 3 commits into from
Mar 21, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -64,7 +64,7 @@ function MonacoCodeEditor({
const isMultiModals = useMemo(() => models.length > 1, [models]);

// Get the container ref and the editor height
const { containerRef, editorHeight } = useEditorHeight();
const { containerRef, headerRef, editorHeight } = useEditorHeight();

useEffect(() => {
// Monaco will be ready after the editor is mounted, useEffect will be called after the monaco is ready
Expand Down Expand Up @@ -108,8 +108,8 @@ function MonacoCodeEditor({
);

return (
<div className={classNames(className, styles.codeEditor)}>
<header>
<div ref={containerRef} className={classNames(className, styles.codeEditor)}>
<header ref={headerRef}>
<div className={styles.tabList}>
{models.map(({ name, title, icon }) => (
<div
Expand Down Expand Up @@ -159,7 +159,7 @@ function MonacoCodeEditor({
)}
</div>
</header>
<div ref={containerRef} className={styles.editorContainer}>
<div className={styles.editorContainer}>
{activeModel && (
<Editor
height={editorHeight}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -5,14 +5,19 @@ import { useRef, useState, useLayoutEffect } from 'react';
// @see {@link https://github.com/react-monaco-editor/react-monaco-editor/issues/391}
const useEditorHeight = () => {
const containerRef = useRef<HTMLDivElement>(null);
const headerRef = useRef<HTMLDivElement>(null);

const [editorHeight, setEditorHeight] = useState<number | string>('100%');
const safeArea = 16;

useLayoutEffect(() => {
const handleResize = () => {
const safeAreaHeight = headerRef.current?.clientHeight
? headerRef.current.clientHeight + safeArea
: safeArea;

if (containerRef.current) {
setEditorHeight(containerRef.current.clientHeight - safeArea);
setEditorHeight(containerRef.current.clientHeight - safeAreaHeight);
}
};

Expand All @@ -29,7 +34,7 @@ const useEditorHeight = () => {
};
}, []);

return { containerRef, editorHeight };
return { containerRef, headerRef, editorHeight };
};

export default useEditorHeight;
39 changes: 29 additions & 10 deletions packages/console/src/pages/JwtClaims/SettingsSection/TestTab.tsx
Original file line number Diff line number Diff line change
@@ -1,21 +1,23 @@
import { LogtoJwtTokenPath } from '@logto/schemas';
import { type JsonObject, LogtoJwtTokenPath } from '@logto/schemas';
import classNames from 'classnames';
import { useCallback, useEffect, useMemo, useState } from 'react';
import { useFormContext, Controller, type ControllerRenderProps } from 'react-hook-form';
import { useTranslation } from 'react-i18next';

import Button from '@/ds-components/Button';
import Card from '@/ds-components/Card';
import useApi from '@/hooks/use-api';

import MonacoCodeEditor, { type ModelControl } from '../MonacoCodeEditor/index.js';
import { type JwtClaimsFormType } from '../type.js';
import MonacoCodeEditor, { type ModelControl } from '../MonacoCodeEditor';
import { type JwtClaimsFormType } from '../type';
import {
accessTokenPayloadTestModel,
clientCredentialsPayloadTestModel,
userContextTestModel,
} from '../utils/config.js';
} from '../utils/config';
import { formatFormDataToTestRequestPayload } from '../utils/format';

import TestResult, { type TestResultData } from './TestResult.js';
import TestResult, { type TestResultData } from './TestResult';
import * as styles from './index.module.scss';

type Props = {
Expand All @@ -24,13 +26,15 @@ type Props = {

const userTokenModelSettings = [accessTokenPayloadTestModel, userContextTestModel];
const machineToMachineTokenModelSettings = [clientCredentialsPayloadTestModel];
const testEndpointPath = 'api/configs/jwt-customizer/test';

function TestTab({ isActive }: Props) {
const { t } = useTranslation(undefined, { keyPrefix: 'admin_console.jwt_claims' });
const [testResult, setTestResult] = useState<TestResultData>();
const [activeModelName, setActiveModelName] = useState<string>();
const api = useApi({ hideErrorToast: true });

const { watch, control, formState } = useFormContext<JwtClaimsFormType>();
const { watch, control, formState, getValues } = useFormContext<JwtClaimsFormType>();
const tokenType = watch('tokenType');

const editorModels = useMemo(
Expand All @@ -45,9 +49,24 @@ function TestTab({ isActive }: Props) {
setActiveModelName(editorModels[0]?.name);
}, [editorModels, tokenType]);

const onTestHandler = useCallback(() => {
// TODO: API integration, read form data and send the request to the server
}, []);
const onTestHandler = useCallback(async () => {
const payload = getValues();

const result = await api
.post(testEndpointPath, {
json: formatFormDataToTestRequestPayload(payload),
})
.json<JsonObject>()
.catch((error: unknown) => {
setTestResult({
error: error instanceof Error ? error.message : String(error),
});
});

if (result) {
setTestResult({ payload: JSON.stringify(result, null, 2) });
}
}, [api, getValues]);

const getModelControllerProps = useCallback(
({ value, onChange }: ControllerRenderProps<JwtClaimsFormType, 'testSample'>): ModelControl => {
Expand Down Expand Up @@ -124,7 +143,7 @@ function TestTab({ isActive }: Props) {
}}
render={({ field }) => (
<MonacoCodeEditor
className={styles.flexGrow}
className={testResult ? styles.shrinkCodeEditor : styles.flexGrow}
enabledActions={['restore', 'copy']}
models={editorModels}
activeModelName={activeModelName}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -150,9 +150,13 @@
margin-bottom: _.unit(4);
}

.shrinkCodeEditor {
height: 50%;
}

.testResult {
margin-top: _.unit(3);
height: calc(50% - _.unit(3));
flex: 1;
background-color: var(--color-bg-layer-2);
border-radius: 8px;
border: 1px solid var(--color-divider);
Expand Down Expand Up @@ -197,5 +201,5 @@
}

.flexGrow {
flex-grow: 1;
flex: 1;
}
6 changes: 3 additions & 3 deletions packages/console/src/pages/JwtClaims/utils/config.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -192,19 +192,19 @@ const standardTokenPayloadData = {
aud: 'http://localhost:3000/api/test',
};

const defaultAccessTokenPayload: AccessTokenPayload = {
export const defaultAccessTokenPayload: AccessTokenPayload = {
...standardTokenPayloadData,
grantId: 'grant_123',
accountId: 'uid_123',
kind: 'AccessToken',
};

const defaultClientCredentialsPayload: ClientCredentialsPayload = {
export const defaultClientCredentialsPayload: ClientCredentialsPayload = {
...standardTokenPayloadData,
kind: 'ClientCredentials',
};

const defaultUserTokenContextData = {
export const defaultUserTokenContextData = {
user: {
id: '123',
primaryEmail: 'foo@logto.io',
Expand Down
34 changes: 33 additions & 1 deletion packages/console/src/pages/JwtClaims/utils/format.ts
Original file line number Diff line number Diff line change
@@ -1,11 +1,17 @@
import {
type LogtoJwtTokenPath,
LogtoJwtTokenPath,
type AccessTokenJwtCustomizer,
type ClientCredentialsJwtCustomizer,
} from '@logto/schemas';

import type { JwtClaimsFormType } from '../type';

import {
defaultAccessTokenPayload,
defaultClientCredentialsPayload,
defaultUserTokenContextData,
} from './config';

const formatEnvVariablesResponseToFormData = (
enVariables?: AccessTokenJwtCustomizer['envVars']
) => {
Expand Down Expand Up @@ -80,5 +86,31 @@ export const formatFormDataToRequestData = (data: JwtClaimsFormType) => {
};
};

export const formatFormDataToTestRequestPayload = ({
tokenType,
script,
environmentVariables,
testSample,
}: JwtClaimsFormType) => {
const defaultTokenSample =
tokenType === LogtoJwtTokenPath.AccessToken
? defaultAccessTokenPayload
: defaultClientCredentialsPayload;

const defaultContextSample =
tokenType === LogtoJwtTokenPath.AccessToken ? defaultUserTokenContextData : undefined;

return {
tokenType,
payload: {
script,
envVars: formatEnvVariablesFormData(environmentVariables),
tokenSample: formatSampleCodeStringToJson(testSample?.tokenSample) ?? defaultTokenSample,
contextSample:
formatSampleCodeStringToJson(testSample?.contextSample) ?? defaultContextSample,
},
};
};

export const getApiPath = (tokenType: LogtoJwtTokenPath) =>
`api/configs/jwt-customizer/${tokenType}`;
2 changes: 1 addition & 1 deletion packages/core/src/libraries/cloud-connection.ts
Original file line number Diff line number Diff line change
Expand Up @@ -28,7 +28,7 @@
* The scope here can be empty and still work, because the cloud API requests made using this client do not rely on scope verification.
* The `CloudScope.SendEmail` is added for now because it needs to call the cloud email service API.
*/
const scopes: string[] = [CloudScope.SendEmail];
const scopes: string[] = [CloudScope.SendEmail, CloudScope.FetchCustomJwt];
const accessTokenExpirationMargin = 60;

/** The library for connecting to Logto Cloud service. */
Expand Down Expand Up @@ -106,7 +106,7 @@
const { endpoint } = await this.getCloudConnectionData();

this.client = new Client<typeof router>({
// TODO @sijie @darcy remove the 'api' appending in getCloudConnectionData()

Check warning on line 109 in packages/core/src/libraries/cloud-connection.ts

View workflow job for this annotation

GitHub Actions / ESLint Report Analysis

packages/core/src/libraries/cloud-connection.ts#L109

[no-warning-comments] Unexpected 'todo' comment: 'TODO @sijie @darcy remove the 'api'...'.
baseUrl: endpoint.replace('/api', ''),
headers: async () => {
return { Authorization: `Bearer ${await this.getAccessToken()}` };
Expand Down
4 changes: 2 additions & 2 deletions packages/core/src/routes/logto-config.ts
Original file line number Diff line number Diff line change
Expand Up @@ -304,14 +304,14 @@ export default function logtoConfigRoutes<T extends AuthedRouter>(
*/
body: z.discriminatedUnion('tokenType', [
z.object({
tokenType: z.literal(LogtoJwtTokenKey.AccessToken),
tokenType: z.literal(LogtoJwtTokenPath.AccessToken),
darcyYe marked this conversation as resolved.
Show resolved Hide resolved
payload: accessTokenJwtCustomizerGuard.required({
script: true,
tokenSample: true,
}),
}),
z.object({
tokenType: z.literal(LogtoJwtTokenKey.ClientCredentials),
tokenType: z.literal(LogtoJwtTokenPath.ClientCredentials),
payload: clientCredentialsJwtCustomizerGuard.required({
script: true,
tokenSample: true,
Expand Down
Loading