From 48cfdf51a37d7e952d47298da2fb526effd0c7aa Mon Sep 17 00:00:00 2001 From: Darcy Ye Date: Fri, 29 Mar 2024 01:50:56 +0800 Subject: [PATCH] chore: update cloud version and the way to call cloud custom JWT API --- .../connector-logto-email/package.json | 2 +- packages/console/package.json | 2 +- packages/core/package.json | 2 +- packages/core/src/oidc/init.ts | 27 ++++++-- packages/core/src/routes/logto-config.ts | 63 +++++++++++-------- packages/schemas/src/types/jwt-customizer.ts | 29 ++++++++- pnpm-lock.yaml | 19 +++--- 7 files changed, 100 insertions(+), 44 deletions(-) diff --git a/packages/connectors/connector-logto-email/package.json b/packages/connectors/connector-logto-email/package.json index 9f52b08ce..51cba7935 100644 --- a/packages/connectors/connector-logto-email/package.json +++ b/packages/connectors/connector-logto-email/package.json @@ -48,6 +48,6 @@ "access": "public" }, "devDependencies": { - "@logto/cloud": "0.2.5-81f06ea" + "@logto/cloud": "0.2.5-2a777a1" } } diff --git a/packages/console/package.json b/packages/console/package.json index 18674ba94..b9e03cb8d 100644 --- a/packages/console/package.json +++ b/packages/console/package.json @@ -28,7 +28,7 @@ "@fontsource/roboto-mono": "^5.0.0", "@jest/types": "^29.5.0", "@logto/app-insights": "workspace:^1.4.0", - "@logto/cloud": "0.2.5-81f06ea", + "@logto/cloud": "0.2.5-2a777a1", "@logto/connector-kit": "workspace:^2.1.0", "@logto/core-kit": "workspace:^2.3.0", "@logto/language-kit": "workspace:^1.1.0", diff --git a/packages/core/package.json b/packages/core/package.json index 53616bfe1..cb97f7f92 100644 --- a/packages/core/package.json +++ b/packages/core/package.json @@ -91,7 +91,7 @@ "zod": "^3.22.4" }, "devDependencies": { - "@logto/cloud": "0.2.5-81f06ea", + "@logto/cloud": "0.2.5-2a777a1", "@silverhand/eslint-config": "5.0.0", "@silverhand/ts-config": "5.0.0", "@types/debug": "^4.1.7", diff --git a/packages/core/src/oidc/init.ts b/packages/core/src/oidc/init.ts index f57f8210b..4f017de34 100644 --- a/packages/core/src/oidc/init.ts +++ b/packages/core/src/oidc/init.ts @@ -14,7 +14,9 @@ import { logtoCookieKey, type LogtoUiCookie, LogtoJwtTokenKey, + LogtoJwtTokenPath, ExtraParamsKey, + type Json, } from '@logto/schemas'; import { conditional, trySafe, tryThat } from '@silverhand/essentials'; import i18next from 'i18next'; @@ -204,6 +206,7 @@ export default function initOidc( }, }, extraParams: Object.values(ExtraParamsKey), + extraTokenClaims: async (ctx, token) => { const { isDevFeaturesEnabled, isCloud } = EnvSet.values; @@ -239,6 +242,12 @@ export default function initOidc( const client = await cloudConnection.getClient(); + const commonPayload = { + script, + envVars, + token: readOnlyToken, + }; + // We pass context to the cloud API only when it is a user's access token. const logtoUserInfo = conditional( !isTokenClientCredentials && @@ -248,12 +257,18 @@ export default function initOidc( // `context` parameter is only eligible for user's access token for now. return await client.post(`/api/services/custom-jwt`, { - body: { - script, - envVars, - token: readOnlyToken, - ...conditional(logtoUserInfo && { context: { user: logtoUserInfo } }), - }, + body: isTokenClientCredentials + ? { + ...commonPayload, + tokenType: LogtoJwtTokenPath.ClientCredentials, + } + : { + ...commonPayload, + tokenType: LogtoJwtTokenPath.AccessToken, + // TODO (LOG-8555): the newly added `UserProfile` type includes undefined fields and can not be directly assigned to `Json` type. And the `undefined` fields should be removed by zod guard. + // eslint-disable-next-line no-restricted-syntax + context: { user: logtoUserInfo as Record }, + }, }); } catch { // TODO: Log the error diff --git a/packages/core/src/routes/logto-config.ts b/packages/core/src/routes/logto-config.ts index efa4a815f..80d159655 100644 --- a/packages/core/src/routes/logto-config.ts +++ b/packages/core/src/routes/logto-config.ts @@ -17,6 +17,9 @@ import { LogtoJwtTokenKey, LogtoJwtTokenPath, jsonObjectGuard, + type CustomJwtFetcher, + jwtCustomizerTestRequestBodyGuard, + type JwtCustomizerTestRequestBody, } from '@logto/schemas'; import { adminTenantId } from '@logto/schemas'; import { ResponseError } from '@withtyped/client'; @@ -50,6 +53,37 @@ const getJwtTokenKeyAndBody = (tokenPath: LogtoJwtTokenPath, body: unknown) => { }; }; +/** + * Transpile the request body of the JWT customizer test API to the request body of the Cloud JWT customizer test API. + * + * @param body Core JWT customizer test API request body. + * @returns Request body of the Cloud JWT customizer test API. + */ +const transpileJwtCustomizerTestRequestBody = ( + body: JwtCustomizerTestRequestBody +): CustomJwtFetcher => { + const { tokenType, payload } = body; + /** + * We have to deal with the `tokenType` and `payload` at the same time since they are put together as one of the discriminated union type. + * Otherwise the type inference will not work as expected. + */ + if (tokenType === LogtoJwtTokenPath.AccessToken) { + const { tokenSample: token, contextSample: context, ...rest } = payload; + return { + tokenType, + token, + context, + ...rest, + }; + } + const { tokenSample: token, contextSample, ...rest } = payload; + return { + tokenType, + token, + ...rest, + }; +}; + /** * Remove actual values of the private keys from response. * @param type Logto config key DB column name. Values are either `oidc.privateKeys` or `oidc.cookieKeys`. @@ -314,41 +348,18 @@ export default function logtoConfigRoutes( * 1. no `script` provided. * 2. no `tokenSample` provided. */ - body: z.discriminatedUnion('tokenType', [ - z.object({ - tokenType: z.literal(LogtoJwtTokenPath.AccessToken), - payload: accessTokenJwtCustomizerGuard.required({ - script: true, - tokenSample: true, - }), - }), - z.object({ - tokenType: z.literal(LogtoJwtTokenPath.ClientCredentials), - payload: clientCredentialsJwtCustomizerGuard.required({ - script: true, - tokenSample: true, - }), - }), - ]), + body: jwtCustomizerTestRequestBodyGuard, response: jsonObjectGuard, status: [200, 400, 403, 422], }), async (ctx, next) => { - const { - body: { - payload: { tokenSample, contextSample, ...rest }, - }, - } = ctx.guard; + const { body } = ctx.guard; const client = await cloudConnection.getClient(); try { ctx.body = await client.post(`/api/services/custom-jwt`, { - body: { - ...rest, - token: tokenSample, - context: contextSample, - }, + body: transpileJwtCustomizerTestRequestBody(body), }); } catch (error: unknown) { /** diff --git a/packages/schemas/src/types/jwt-customizer.ts b/packages/schemas/src/types/jwt-customizer.ts index 2c7fbc3d3..492a2e7b0 100644 --- a/packages/schemas/src/types/jwt-customizer.ts +++ b/packages/schemas/src/types/jwt-customizer.ts @@ -3,7 +3,11 @@ import { z } from 'zod'; import { Roles, UserSsoIdentities, Organizations } from '../db-entries/index.js'; import { jsonObjectGuard, mfaFactorsGuard } from '../foundations/index.js'; -import { jwtCustomizerGuard } from './logto-config/index.js'; +import { + jwtCustomizerGuard, + accessTokenJwtCustomizerGuard, + clientCredentialsJwtCustomizerGuard, +} from './logto-config/index.js'; import { scopeResponseGuard } from './scope.js'; import { userInfoGuard } from './user.js'; @@ -37,6 +41,29 @@ export enum LogtoJwtTokenPath { ClientCredentials = 'client-credentials', } +/** + * This guard is for the core JWT customizer testing API request body guard. + */ +export const jwtCustomizerTestRequestBodyGuard = z.discriminatedUnion('tokenType', [ + z.object({ + tokenType: z.literal(LogtoJwtTokenPath.AccessToken), + payload: accessTokenJwtCustomizerGuard.required({ + script: true, + tokenSample: true, + contextSample: true, + }), + }), + z.object({ + tokenType: z.literal(LogtoJwtTokenPath.ClientCredentials), + payload: clientCredentialsJwtCustomizerGuard.required({ + script: true, + tokenSample: true, + }), + }), +]); + +export type JwtCustomizerTestRequestBody = z.infer; + /** * This guard is for cloud API use (request body guard). * Since the cloud API will be use by both testing and production, should keep the fields as general as possible. diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index f4d4db44d..6759f56db 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -1235,8 +1235,8 @@ importers: version: 3.22.4 devDependencies: '@logto/cloud': - specifier: 0.2.5-81f06ea - version: 0.2.5-81f06ea(zod@3.22.4) + specifier: 0.2.5-2a777a1 + version: 0.2.5-2a777a1(zod@3.22.4) '@rollup/plugin-commonjs': specifier: ^25.0.0 version: 25.0.7(rollup@4.12.0) @@ -2715,8 +2715,8 @@ importers: specifier: workspace:^1.4.0 version: link:../app-insights '@logto/cloud': - specifier: 0.2.5-81f06ea - version: 0.2.5-81f06ea(zod@3.22.4) + specifier: 0.2.5-2a777a1 + version: 0.2.5-2a777a1(zod@3.22.4) '@logto/connector-kit': specifier: workspace:^2.1.0 version: link:../toolkit/connector-kit @@ -3202,8 +3202,8 @@ importers: version: 3.22.4 devDependencies: '@logto/cloud': - specifier: 0.2.5-81f06ea - version: 0.2.5-81f06ea(zod@3.22.4) + specifier: 0.2.5-2a777a1 + version: 0.2.5-2a777a1(zod@3.22.4) '@silverhand/eslint-config': specifier: 5.0.0 version: 5.0.0(eslint@8.44.0)(prettier@3.0.0)(typescript@5.3.3) @@ -7647,8 +7647,8 @@ packages: jose: 5.2.2 dev: true - /@logto/cloud@0.2.5-81f06ea(zod@3.22.4): - resolution: {integrity: sha512-7u2VY8qlRoaheWDEbHdoFmQP9MbloKuuCwbz1jk+Wrn2EE1v+tgixVK/MiyFaAN5mLAVLAlCVQ00JIabw+g6YA==} + /@logto/cloud@0.2.5-2a777a1(zod@3.22.4): + resolution: {integrity: sha512-RnU13Hrv5phYtIjVHDo0Ik1ZFvEOT5XBdQ0fDOHBFuGH+1Xd4X4HK79Mm5iC5JMM7KxxuH7bb6lStCvsOkUUYw==} engines: {node: ^20.9.0} dependencies: '@silverhand/essentials': 2.9.0 @@ -18004,6 +18004,9 @@ packages: resolution: {integrity: sha512-2GTVocFkwblV/TIg9AmT7TI2fO4xdWkyN8aFUEVtiVNWt96GTR3FgQyHFValfCbcj1k9Xf962Ws2hYXYUr9k1Q==} engines: {node: '>= 12.0.0'} hasBin: true + peerDependenciesMeta: + '@parcel/core': + optional: true dependencies: '@parcel/config-default': 2.9.3(@parcel/core@2.9.3)(postcss@8.4.31) '@parcel/core': 2.9.3