From c15c2ac10491596e8a31daa77b660a2383f25c70 Mon Sep 17 00:00:00 2001 From: simeng-li Date: Wed, 31 Jul 2024 16:14:55 +0800 Subject: [PATCH 1/7] feat(core,console,phrases): add custom data editor to application details page add custom data editor to application details page --- .../ApplicationDetailsContent/Settings.tsx | 15 +++- .../ApplicationDetailsContent/index.tsx | 28 ++++-- .../ApplicationDetailsContent/utils.ts | 87 +++++++++++-------- packages/core/src/queries/application.ts | 7 +- .../applications/application-custom-data.ts | 4 +- .../src/routes/applications/application.ts | 2 +- .../application-custom-data.test.ts | 10 ++- .../admin-console/application-details.ts | 4 + 8 files changed, 102 insertions(+), 55 deletions(-) diff --git a/packages/console/src/pages/ApplicationDetails/ApplicationDetailsContent/Settings.tsx b/packages/console/src/pages/ApplicationDetails/ApplicationDetailsContent/Settings.tsx index 3c68077fdb2..d02bc9db9c9 100644 --- a/packages/console/src/pages/ApplicationDetails/ApplicationDetailsContent/Settings.tsx +++ b/packages/console/src/pages/ApplicationDetails/ApplicationDetailsContent/Settings.tsx @@ -1,22 +1,24 @@ import { validateRedirectUrl } from '@logto/core-kit'; import type { Application } from '@logto/schemas'; import { ApplicationType } from '@logto/schemas'; -import { Controller, useFormContext } from 'react-hook-form'; +import { Controller, useController, useFormContext } from 'react-hook-form'; import { Trans, useTranslation } from 'react-i18next'; import FormCard from '@/components/FormCard'; import MultiTextInputField from '@/components/MultiTextInputField'; +import CodeEditor from '@/ds-components/CodeEditor'; import FormField from '@/ds-components/FormField'; import type { MultiTextInputRule } from '@/ds-components/MultiTextInput/types'; import { - createValidatorForRhf, convertRhfErrorMessage, + createValidatorForRhf, } from '@/ds-components/MultiTextInput/utils'; import TextInput from '@/ds-components/TextInput'; import TextLink from '@/ds-components/TextLink'; import useDocumentationUrl from '@/hooks/use-documentation-url'; import ProtectedAppSettings from './ProtectedAppSettings'; +import { type ApplicationForm } from './utils'; type Props = { readonly data: Application; @@ -29,9 +31,10 @@ function Settings({ data }: Props) { control, register, formState: { errors }, - } = useFormContext(); + } = useFormContext(); const { type: applicationType } = data; + const { field: customData } = useController({ name: 'customData', control }); const isNativeApp = applicationType === ApplicationType.Native; const isProtectedApp = applicationType === ApplicationType.Protected; @@ -161,6 +164,12 @@ function Settings({ data }: Props) { )} /> )} + + + ); } diff --git a/packages/console/src/pages/ApplicationDetails/ApplicationDetailsContent/index.tsx b/packages/console/src/pages/ApplicationDetails/ApplicationDetailsContent/index.tsx index 2a2db9a37ef..81ac8561ef3 100644 --- a/packages/console/src/pages/ApplicationDetails/ApplicationDetailsContent/index.tsx +++ b/packages/console/src/pages/ApplicationDetails/ApplicationDetailsContent/index.tsx @@ -40,7 +40,7 @@ import Permissions from './Permissions'; import RefreshTokenSettings from './RefreshTokenSettings'; import Settings from './Settings'; import styles from './index.module.scss'; -import { type ApplicationForm, applicationFormDataParser } from './utils'; +import { applicationFormDataParser, type ApplicationForm } from './utils'; type Props = { readonly data: ApplicationResponse; @@ -84,14 +84,24 @@ function ApplicationDetailsContent({ data, secrets, oidcConfig, onApplicationUpd return; } - const updatedData = await api - .patch(`api/applications/${data.id}`, { - json: applicationFormDataParser.toRequestPayload(formData), - }) - .json(); - reset(applicationFormDataParser.fromResponse(updatedData)); - onApplicationUpdated(); - toast.success(t('general.saved')); + const [error, result] = applicationFormDataParser.toRequestPayload(formData); + + if (result) { + const updatedData = await api + .patch(`api/applications/${data.id}`, { + json: result, + }) + .json(); + + reset(applicationFormDataParser.fromResponse(updatedData)); + onApplicationUpdated(); + toast.success(t('general.saved')); + return; + } + + if (error) { + toast.error(String(t(error))); + } }) ); diff --git a/packages/console/src/pages/ApplicationDetails/ApplicationDetailsContent/utils.ts b/packages/console/src/pages/ApplicationDetails/ApplicationDetailsContent/utils.ts index 145eae866be..97501b0e95c 100644 --- a/packages/console/src/pages/ApplicationDetails/ApplicationDetailsContent/utils.ts +++ b/packages/console/src/pages/ApplicationDetails/ApplicationDetailsContent/utils.ts @@ -1,5 +1,8 @@ +import { type AdminConsoleKey } from '@logto/phrases'; import { customClientMetadataDefault, type ApplicationResponse } from '@logto/schemas'; -import { type DeepPartial, cond } from '@silverhand/essentials'; +import { cond, type DeepPartial, type Nullable } from '@silverhand/essentials'; + +import { safeParseJsonObject } from '@/utils/json'; type ProtectedAppMetadataType = ApplicationResponse['protectedAppMetadata']; @@ -11,6 +14,7 @@ export type ApplicationForm = { isAdmin?: ApplicationResponse['isAdmin']; // eslint-disable-next-line @typescript-eslint/ban-types protectedAppMetadata?: Omit, 'customDomains'>; // Custom domains are handled separately + customData: string; }; const mapToUriFormatArrays = (value?: string[]) => @@ -29,6 +33,7 @@ export const applicationFormDataParser = { isAdmin, /** Specific metadata for protected apps */ protectedAppMetadata, + customData, } = data; return { @@ -52,9 +57,12 @@ export const applicationFormDataParser = { }, } ), + customData: JSON.stringify(customData, null, 2), }; }, - toRequestPayload: (data: ApplicationForm): DeepPartial => { + toRequestPayload: ( + data: ApplicationForm + ): [Nullable, DeepPartial?] => { const { name, description, @@ -62,39 +70,50 @@ export const applicationFormDataParser = { customClientMetadata, isAdmin, protectedAppMetadata, + customData, } = data; - return { - name, - ...cond( - !protectedAppMetadata && { - description, - oidcClientMetadata: { - ...oidcClientMetadata, - redirectUris: mapToUriFormatArrays(oidcClientMetadata?.redirectUris), - postLogoutRedirectUris: mapToUriFormatArrays( - oidcClientMetadata?.postLogoutRedirectUris - ), - // Empty string is not a valid URL - backchannelLogoutUri: cond(oidcClientMetadata?.backchannelLogoutUri), - }, - customClientMetadata: { - ...customClientMetadata, - corsAllowedOrigins: mapToUriOriginFormatArrays( - customClientMetadata?.corsAllowedOrigins - ), - }, - isAdmin, - } - ), - ...cond( - protectedAppMetadata && { - protectedAppMetadata: { - ...protectedAppMetadata, - sessionDuration: protectedAppMetadata.sessionDuration * 3600 * 24, - }, - } - ), - }; + const parsedCustomData = safeParseJsonObject(customData); + + if (!parsedCustomData.success) { + return ['application_details.custom_data_invalid']; + } + + return [ + null, + { + name, + ...cond( + !protectedAppMetadata && { + description, + oidcClientMetadata: { + ...oidcClientMetadata, + redirectUris: mapToUriFormatArrays(oidcClientMetadata?.redirectUris), + postLogoutRedirectUris: mapToUriFormatArrays( + oidcClientMetadata?.postLogoutRedirectUris + ), + // Empty string is not a valid URL + backchannelLogoutUri: cond(oidcClientMetadata?.backchannelLogoutUri), + }, + customClientMetadata: { + ...customClientMetadata, + corsAllowedOrigins: mapToUriOriginFormatArrays( + customClientMetadata?.corsAllowedOrigins + ), + }, + customData: parsedCustomData.data, + isAdmin, + } + ), + ...cond( + protectedAppMetadata && { + protectedAppMetadata: { + ...protectedAppMetadata, + sessionDuration: protectedAppMetadata.sessionDuration * 3600 * 24, + }, + } + ), + }, + ]; }, }; diff --git a/packages/core/src/queries/application.ts b/packages/core/src/queries/application.ts index 12d8a48ce44..f5f46a32b25 100644 --- a/packages/core/src/queries/application.ts +++ b/packages/core/src/queries/application.ts @@ -14,10 +14,10 @@ import { buildInsertIntoWithPool } from '#src/database/insert-into.js'; import { getTotalRowCountWithPool } from '#src/database/row-count.js'; import { buildUpdateWhereWithPool } from '#src/database/update-where.js'; import { DeletionError } from '#src/errors/SlonikError/index.js'; -import { buildConditionsFromSearch } from '#src/utils/search.js'; import type { Search } from '#src/utils/search.js'; -import { convertToIdentifiers, conditionalSql, conditionalArraySql } from '#src/utils/sql.js'; +import { buildConditionsFromSearch } from '#src/utils/search.js'; import type { OmitAutoSetFields } from '#src/utils/sql.js'; +import { conditionalArraySql, conditionalSql, convertToIdentifiers } from '#src/utils/sql.js'; import ApplicationUserConsentOrganizationsQuery from './application-user-consent-organizations.js'; import { @@ -145,7 +145,8 @@ export const createApplicationQueries = (pool: CommonQueryMethods) => { const updateApplicationById = async ( id: string, - set: Partial> + set: Partial>, + jsonbMode: 'merge' | 'replace' = 'merge' ) => updateApplication({ set, where: { id }, jsonbMode: 'merge' }); const countAllApplications = async () => diff --git a/packages/core/src/routes/applications/application-custom-data.ts b/packages/core/src/routes/applications/application-custom-data.ts index 6af47260b2f..d4c6f21f2b1 100644 --- a/packages/core/src/routes/applications/application-custom-data.ts +++ b/packages/core/src/routes/applications/application-custom-data.ts @@ -19,10 +19,8 @@ export default function applicationCustomDataRoutes( } ctx.body = await (Object.keys(rest).length > 0 - ? queries.applications.updateApplicationById(id, rest) + ? queries.applications.updateApplicationById(id, rest, 'replace') : queries.applications.findApplicationById(id)); return next(); diff --git a/packages/integration-tests/src/tests/api/application/application-custom-data.test.ts b/packages/integration-tests/src/tests/api/application/application-custom-data.test.ts index 508ec08a09c..ccd860fb87f 100644 --- a/packages/integration-tests/src/tests/api/application/application-custom-data.test.ts +++ b/packages/integration-tests/src/tests/api/application/application-custom-data.test.ts @@ -23,6 +23,12 @@ describe('application custom data API', () => { expect(fetchedApplication.customData.key).toEqual(customData.key); + await patchApplicationCustomData(application.id, { key: 'new-value', test: 'foo' }); + + const updatedApplication = await getApplication(application.id); + expect(updatedApplication.customData.key).toEqual('new-value'); + expect(updatedApplication.customData.test).toEqual('foo'); + await deleteApplication(application.id); }); @@ -37,10 +43,10 @@ describe('application custom data API', () => { expect(result.key).toEqual(customData.key); await updateApplication(application.id, { - customData: { key: 'bar' }, + customData: {}, }); const fetchedApplication = await getApplication(application.id); - expect(fetchedApplication.customData.key).toEqual('bar'); + expect(Object.keys(fetchedApplication.customData)).toHaveLength(0); }); }); diff --git a/packages/phrases/src/locales/en/translation/admin-console/application-details.ts b/packages/phrases/src/locales/en/translation/admin-console/application-details.ts index a276f7c0ab1..242a7a75d39 100644 --- a/packages/phrases/src/locales/en/translation/admin-console/application-details.ts +++ b/packages/phrases/src/locales/en/translation/admin-console/application-details.ts @@ -94,6 +94,10 @@ const application_details = { session_duration: 'Session duration (days)', try_it: 'Try it', no_organization_placeholder: 'No organization found. Go to organizations', + field_custom_data: 'Custom data', + field_custom_data_tip: + 'Additional custom application metadata not listed in the pre-defined application properties, ', + custom_data_invalid: 'Custom data must be a valid JSON object', branding: { name: 'Branding', description: 'Customize your app logo and branding color for the app-level experience.', From e417ee1db955f5276191347ce483b8051b289e93 Mon Sep 17 00:00:00 2001 From: simeng-li Date: Wed, 31 Jul 2024 16:28:37 +0800 Subject: [PATCH 2/7] chore: add changeset add changeset --- .changeset/gold-mails-sin.md | 6 ++++++ .changeset/sweet-rules-hear.md | 8 ++++++++ 2 files changed, 14 insertions(+) create mode 100644 .changeset/gold-mails-sin.md create mode 100644 .changeset/sweet-rules-hear.md diff --git a/.changeset/gold-mails-sin.md b/.changeset/gold-mails-sin.md new file mode 100644 index 00000000000..a8da2b2d01f --- /dev/null +++ b/.changeset/gold-mails-sin.md @@ -0,0 +1,6 @@ +--- +"@logto/console": minor +"@logto/phrases": minor +--- + +add the application custom_data field editor to the application details page in console diff --git a/.changeset/sweet-rules-hear.md b/.changeset/sweet-rules-hear.md new file mode 100644 index 00000000000..0a30a8c57b3 --- /dev/null +++ b/.changeset/sweet-rules-hear.md @@ -0,0 +1,8 @@ +--- +"@logto/core": minor +--- + +update the jsonb field update mode from 'merge' to 'replace' for the PATCH /application/:id endpoint. + +For all the jsonb typed fields in the application entity, the update mode is now 'replace' instead of 'merge'. This means that when you send a PATCH request to update an application, the jsonb fields will be replaced with the new values instead of merging them. +This change is to make the request behavior more strict aligned with the restful API principles for a PATCH request. From 333b879aed69bfeb54a9e96c9282b332508f5deb Mon Sep 17 00:00:00 2001 From: simeng-li Date: Wed, 31 Jul 2024 16:48:31 +0800 Subject: [PATCH 3/7] fix(core): fix input params bug fix input params bug --- packages/core/src/queries/application.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/core/src/queries/application.ts b/packages/core/src/queries/application.ts index f5f46a32b25..3f060e12d6c 100644 --- a/packages/core/src/queries/application.ts +++ b/packages/core/src/queries/application.ts @@ -147,7 +147,7 @@ export const createApplicationQueries = (pool: CommonQueryMethods) => { id: string, set: Partial>, jsonbMode: 'merge' | 'replace' = 'merge' - ) => updateApplication({ set, where: { id }, jsonbMode: 'merge' }); + ) => updateApplication({ set, where: { id }, jsonbMode }); const countAllApplications = async () => countApplications({ From 1d3d50e65fb48f032f4c8704249b1bd5451b5b89 Mon Sep 17 00:00:00 2001 From: simeng-li Date: Thu, 1 Aug 2024 10:30:29 +0800 Subject: [PATCH 4/7] fix(test): fix the integration tests fix the integration tests --- .../src/tests/api/application/application.test.ts | 5 +++-- .../src/tests/console/backchannel-logout.test.ts | 5 ++++- 2 files changed, 7 insertions(+), 3 deletions(-) diff --git a/packages/integration-tests/src/tests/api/application/application.test.ts b/packages/integration-tests/src/tests/api/application/application.test.ts index 346d9ba105e..6093436f26b 100644 --- a/packages/integration-tests/src/tests/api/application/application.test.ts +++ b/packages/integration-tests/src/tests/api/application/application.test.ts @@ -3,10 +3,10 @@ import { HTTPError } from 'ky'; import { createApplication, - getApplication, - updateApplication, deleteApplication, + getApplication, getApplications, + updateApplication, } from '#src/api/index.js'; import { expectRejects } from '#src/helpers/index.js'; @@ -108,6 +108,7 @@ describe('application APIs', () => { await updateApplication(application.id, { description: newApplicationDescription, oidcClientMetadata: { + ...application.oidcClientMetadata, redirectUris: newRedirectUris, }, customClientMetadata: { rotateRefreshToken: true, refreshTokenTtlInDays: 10 }, diff --git a/packages/integration-tests/src/tests/console/backchannel-logout.test.ts b/packages/integration-tests/src/tests/console/backchannel-logout.test.ts index ff505d7f7fa..4825b080355 100644 --- a/packages/integration-tests/src/tests/console/backchannel-logout.test.ts +++ b/packages/integration-tests/src/tests/console/backchannel-logout.test.ts @@ -17,11 +17,12 @@ * from the Console and check if the backchannel logout endpoint is called. */ -import { type Server, type RequestListener, createServer } from 'node:http'; +import { createServer, type RequestListener, type Server } from 'node:http'; import { adminConsoleApplicationId } from '@logto/schemas'; import { authedAdminTenantApi } from '#src/api/api.js'; +import { getApplication } from '#src/api/application.js'; import ExpectConsole from '#src/ui-helpers/expect-console.js'; import { waitFor } from '#src/utils.js'; @@ -91,9 +92,11 @@ describe('backchannel logout', () => { }); it('should call the backchannel logout endpoint when a user logs out', async () => { + const application = await getApplication(adminConsoleApplicationId); await authedAdminTenantApi.patch('applications/' + adminConsoleApplicationId, { json: { oidcClientMetadata: { + ...application.oidcClientMetadata, backchannelLogoutUri, }, }, From 9051f37e1c6f77947e17b83565f62e1d4e7b1791 Mon Sep 17 00:00:00 2001 From: simeng-li Date: Thu, 1 Aug 2024 10:56:41 +0800 Subject: [PATCH 5/7] fix(console): use the form controller element use the form controller element --- .changeset/gold-mails-sin.md | 2 +- .changeset/sweet-rules-hear.md | 7 +++--- .../ApplicationDetailsContent/Settings.tsx | 22 ++++++++++++------- 3 files changed, 19 insertions(+), 12 deletions(-) diff --git a/.changeset/gold-mails-sin.md b/.changeset/gold-mails-sin.md index a8da2b2d01f..eb3efbd242c 100644 --- a/.changeset/gold-mails-sin.md +++ b/.changeset/gold-mails-sin.md @@ -3,4 +3,4 @@ "@logto/phrases": minor --- -add the application custom_data field editor to the application details page in console +add the application `custom_data` field editor to the application details page in console diff --git a/.changeset/sweet-rules-hear.md b/.changeset/sweet-rules-hear.md index 0a30a8c57b3..745bcb622a1 100644 --- a/.changeset/sweet-rules-hear.md +++ b/.changeset/sweet-rules-hear.md @@ -2,7 +2,8 @@ "@logto/core": minor --- -update the jsonb field update mode from 'merge' to 'replace' for the PATCH /application/:id endpoint. +update the jsonb field update mode from `merge` to `replace` for the `PATCH /application/:id endpoint` -For all the jsonb typed fields in the application entity, the update mode is now 'replace' instead of 'merge'. This means that when you send a PATCH request to update an application, the jsonb fields will be replaced with the new values instead of merging them. -This change is to make the request behavior more strict aligned with the restful API principles for a PATCH request. +For all the jsonb typed fields in the application entity, the update mode is now `replace` instead of `merge`. This means that when you send a `PATCH` request to update an application, the jsonb fields will be replaced with the new values instead of merging them. + +This change is to make the request behavior more strict aligned with the restful API principles for a `PATCH` request. diff --git a/packages/console/src/pages/ApplicationDetails/ApplicationDetailsContent/Settings.tsx b/packages/console/src/pages/ApplicationDetails/ApplicationDetailsContent/Settings.tsx index d02bc9db9c9..09f31626563 100644 --- a/packages/console/src/pages/ApplicationDetails/ApplicationDetailsContent/Settings.tsx +++ b/packages/console/src/pages/ApplicationDetails/ApplicationDetailsContent/Settings.tsx @@ -1,7 +1,7 @@ import { validateRedirectUrl } from '@logto/core-kit'; import type { Application } from '@logto/schemas'; import { ApplicationType } from '@logto/schemas'; -import { Controller, useController, useFormContext } from 'react-hook-form'; +import { Controller, useFormContext } from 'react-hook-form'; import { Trans, useTranslation } from 'react-i18next'; import FormCard from '@/components/FormCard'; @@ -34,7 +34,6 @@ function Settings({ data }: Props) { } = useFormContext(); const { type: applicationType } = data; - const { field: customData } = useController({ name: 'customData', control }); const isNativeApp = applicationType === ApplicationType.Native; const isProtectedApp = applicationType === ApplicationType.Protected; @@ -164,12 +163,19 @@ function Settings({ data }: Props) { )} /> )} - - - + ( + + + + )} + /> ); } From 6989bf5945faaa4ed7dc1491e9904d734669d2e6 Mon Sep 17 00:00:00 2001 From: simeng-li Date: Thu, 1 Aug 2024 11:21:39 +0800 Subject: [PATCH 6/7] fix(core,console): remove deepPartial statement remove deepPartial statement from the patch application API payload guard --- .changeset/sweet-rules-hear.md | 3 ++- .../web-gpt-plugin/components/AlwaysIssueRefreshToken.tsx | 1 + packages/console/src/mdx-components/UriInputField/index.tsx | 3 ++- packages/core/src/routes/applications/types.ts | 1 - 4 files changed, 5 insertions(+), 3 deletions(-) diff --git a/.changeset/sweet-rules-hear.md b/.changeset/sweet-rules-hear.md index 745bcb622a1..6f8b170023b 100644 --- a/.changeset/sweet-rules-hear.md +++ b/.changeset/sweet-rules-hear.md @@ -2,7 +2,8 @@ "@logto/core": minor --- -update the jsonb field update mode from `merge` to `replace` for the `PATCH /application/:id endpoint` +update the jsonb field update mode from `merge` to `replace` for the `PATCH /application/:id` endpoint. +remove the `deepPartial` statement from the `PATCH /application/:id` endpoint payload guard. For all the jsonb typed fields in the application entity, the update mode is now `replace` instead of `merge`. This means that when you send a `PATCH` request to update an application, the jsonb fields will be replaced with the new values instead of merging them. diff --git a/packages/console/src/assets/docs/guides/web-gpt-plugin/components/AlwaysIssueRefreshToken.tsx b/packages/console/src/assets/docs/guides/web-gpt-plugin/components/AlwaysIssueRefreshToken.tsx index 858c8fe0f8d..9a446392cda 100644 --- a/packages/console/src/assets/docs/guides/web-gpt-plugin/components/AlwaysIssueRefreshToken.tsx +++ b/packages/console/src/assets/docs/guides/web-gpt-plugin/components/AlwaysIssueRefreshToken.tsx @@ -23,6 +23,7 @@ export default function AlwaysIssueRefreshToken() { await api.patch(`api/applications/${app.id}`, { json: { customClientMetadata: { + ...app.customClientMetadata, alwaysIssueRefreshToken: value, }, }, diff --git a/packages/console/src/mdx-components/UriInputField/index.tsx b/packages/console/src/mdx-components/UriInputField/index.tsx index f38915c770c..ce864dc3fdc 100644 --- a/packages/console/src/mdx-components/UriInputField/index.tsx +++ b/packages/console/src/mdx-components/UriInputField/index.tsx @@ -70,13 +70,14 @@ function UriInputField(props: Props) { const title: AdminConsoleKey = nameToKey[name]; const onSubmit = trySubmitSafe(async (value: string[]) => { - if (!appId) { + if (!appId || !data) { return; } const updatedApp = await api .patch(`api/applications/${appId}`, { json: { [type]: { + ...data[type], [name]: value.filter(Boolean), }, }, diff --git a/packages/core/src/routes/applications/types.ts b/packages/core/src/routes/applications/types.ts index 135e64b643c..33ca10fc99b 100644 --- a/packages/core/src/routes/applications/types.ts +++ b/packages/core/src/routes/applications/types.ts @@ -18,7 +18,6 @@ export const applicationCreateGuard = originalApplicationCreateGuard }); export const applicationPatchGuard = originalApplicationPatchGuard - .deepPartial() .omit({ protectedAppMetadata: true, }) From e12bd0cd1e8394716b1dd39df52681696a783c10 Mon Sep 17 00:00:00 2001 From: simeng-li Date: Thu, 1 Aug 2024 13:31:24 +0800 Subject: [PATCH 7/7] fix(test): fix backchannel integration test fix backchannel integration test --- .../src/tests/console/backchannel-logout.test.ts | 8 +++++--- 1 file changed, 5 insertions(+), 3 deletions(-) diff --git a/packages/integration-tests/src/tests/console/backchannel-logout.test.ts b/packages/integration-tests/src/tests/console/backchannel-logout.test.ts index 4825b080355..27e807bf078 100644 --- a/packages/integration-tests/src/tests/console/backchannel-logout.test.ts +++ b/packages/integration-tests/src/tests/console/backchannel-logout.test.ts @@ -19,10 +19,9 @@ import { createServer, type RequestListener, type Server } from 'node:http'; -import { adminConsoleApplicationId } from '@logto/schemas'; +import { adminConsoleApplicationId, type Application } from '@logto/schemas'; import { authedAdminTenantApi } from '#src/api/api.js'; -import { getApplication } from '#src/api/application.js'; import ExpectConsole from '#src/ui-helpers/expect-console.js'; import { waitFor } from '#src/utils.js'; @@ -92,7 +91,10 @@ describe('backchannel logout', () => { }); it('should call the backchannel logout endpoint when a user logs out', async () => { - const application = await getApplication(adminConsoleApplicationId); + const application = await authedAdminTenantApi + .get('applications/' + adminConsoleApplicationId) + .json(); + await authedAdminTenantApi.patch('applications/' + adminConsoleApplicationId, { json: { oidcClientMetadata: {