0
Fork 0
mirror of https://github.com/logto-io/logto.git synced 2024-12-16 20:26:19 -05:00

Merge pull request #5585 from logto-io/yemq-log-8483-update-cloud-client-API-call

chore: update cloud version and the way to call cloud custom JWT API
This commit is contained in:
Darcy Ye 2024-03-29 14:32:55 +08:00 committed by GitHub
commit a77fd3f97f
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
7 changed files with 100 additions and 44 deletions

View file

@ -48,6 +48,6 @@
"access": "public" "access": "public"
}, },
"devDependencies": { "devDependencies": {
"@logto/cloud": "0.2.5-81f06ea" "@logto/cloud": "0.2.5-2a777a1"
} }
} }

View file

@ -28,7 +28,7 @@
"@fontsource/roboto-mono": "^5.0.0", "@fontsource/roboto-mono": "^5.0.0",
"@jest/types": "^29.5.0", "@jest/types": "^29.5.0",
"@logto/app-insights": "workspace:^1.4.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/connector-kit": "workspace:^2.1.0",
"@logto/core-kit": "workspace:^2.3.0", "@logto/core-kit": "workspace:^2.3.0",
"@logto/language-kit": "workspace:^1.1.0", "@logto/language-kit": "workspace:^1.1.0",

View file

@ -91,7 +91,7 @@
"zod": "^3.22.4" "zod": "^3.22.4"
}, },
"devDependencies": { "devDependencies": {
"@logto/cloud": "0.2.5-81f06ea", "@logto/cloud": "0.2.5-2a777a1",
"@silverhand/eslint-config": "5.0.0", "@silverhand/eslint-config": "5.0.0",
"@silverhand/ts-config": "5.0.0", "@silverhand/ts-config": "5.0.0",
"@types/debug": "^4.1.7", "@types/debug": "^4.1.7",

View file

@ -14,7 +14,9 @@ import {
logtoCookieKey, logtoCookieKey,
type LogtoUiCookie, type LogtoUiCookie,
LogtoJwtTokenKey, LogtoJwtTokenKey,
LogtoJwtTokenPath,
ExtraParamsKey, ExtraParamsKey,
type Json,
} from '@logto/schemas'; } from '@logto/schemas';
import { conditional, trySafe, tryThat } from '@silverhand/essentials'; import { conditional, trySafe, tryThat } from '@silverhand/essentials';
import i18next from 'i18next'; import i18next from 'i18next';
@ -204,6 +206,7 @@ export default function initOidc(
}, },
}, },
extraParams: Object.values(ExtraParamsKey), extraParams: Object.values(ExtraParamsKey),
extraTokenClaims: async (ctx, token) => { extraTokenClaims: async (ctx, token) => {
const { isDevFeaturesEnabled, isCloud } = EnvSet.values; const { isDevFeaturesEnabled, isCloud } = EnvSet.values;
@ -239,6 +242,12 @@ export default function initOidc(
const client = await cloudConnection.getClient(); 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. // We pass context to the cloud API only when it is a user's access token.
const logtoUserInfo = conditional( const logtoUserInfo = conditional(
!isTokenClientCredentials && !isTokenClientCredentials &&
@ -248,12 +257,18 @@ export default function initOidc(
// `context` parameter is only eligible for user's access token for now. // `context` parameter is only eligible for user's access token for now.
return await client.post(`/api/services/custom-jwt`, { return await client.post(`/api/services/custom-jwt`, {
body: { body: isTokenClientCredentials
script, ? {
envVars, ...commonPayload,
token: readOnlyToken, tokenType: LogtoJwtTokenPath.ClientCredentials,
...conditional(logtoUserInfo && { context: { user: logtoUserInfo } }), }
}, : {
...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<string, Json> },
},
}); });
} catch { } catch {
// TODO: Log the error // TODO: Log the error

View file

@ -17,6 +17,9 @@ import {
LogtoJwtTokenKey, LogtoJwtTokenKey,
LogtoJwtTokenPath, LogtoJwtTokenPath,
jsonObjectGuard, jsonObjectGuard,
type CustomJwtFetcher,
jwtCustomizerTestRequestBodyGuard,
type JwtCustomizerTestRequestBody,
} from '@logto/schemas'; } from '@logto/schemas';
import { adminTenantId } from '@logto/schemas'; import { adminTenantId } from '@logto/schemas';
import { ResponseError } from '@withtyped/client'; 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. * 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`. * @param type Logto config key DB column name. Values are either `oidc.privateKeys` or `oidc.cookieKeys`.
@ -314,41 +348,18 @@ export default function logtoConfigRoutes<T extends AuthedRouter>(
* 1. no `script` provided. * 1. no `script` provided.
* 2. no `tokenSample` provided. * 2. no `tokenSample` provided.
*/ */
body: z.discriminatedUnion('tokenType', [ body: jwtCustomizerTestRequestBodyGuard,
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,
}),
}),
]),
response: jsonObjectGuard, response: jsonObjectGuard,
status: [200, 400, 403, 422], status: [200, 400, 403, 422],
}), }),
async (ctx, next) => { async (ctx, next) => {
const { const { body } = ctx.guard;
body: {
payload: { tokenSample, contextSample, ...rest },
},
} = ctx.guard;
const client = await cloudConnection.getClient(); const client = await cloudConnection.getClient();
try { try {
ctx.body = await client.post(`/api/services/custom-jwt`, { ctx.body = await client.post(`/api/services/custom-jwt`, {
body: { body: transpileJwtCustomizerTestRequestBody(body),
...rest,
token: tokenSample,
context: contextSample,
},
}); });
} catch (error: unknown) { } catch (error: unknown) {
/** /**

View file

@ -3,7 +3,11 @@ import { z } from 'zod';
import { Roles, UserSsoIdentities, Organizations } from '../db-entries/index.js'; import { Roles, UserSsoIdentities, Organizations } from '../db-entries/index.js';
import { jsonObjectGuard, mfaFactorsGuard } from '../foundations/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 { scopeResponseGuard } from './scope.js';
import { userInfoGuard } from './user.js'; import { userInfoGuard } from './user.js';
@ -37,6 +41,29 @@ export enum LogtoJwtTokenPath {
ClientCredentials = 'client-credentials', 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<typeof jwtCustomizerTestRequestBodyGuard>;
/** /**
* This guard is for cloud API use (request body guard). * 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. * Since the cloud API will be use by both testing and production, should keep the fields as general as possible.

View file

@ -1235,8 +1235,8 @@ importers:
version: 3.22.4 version: 3.22.4
devDependencies: devDependencies:
'@logto/cloud': '@logto/cloud':
specifier: 0.2.5-81f06ea specifier: 0.2.5-2a777a1
version: 0.2.5-81f06ea(zod@3.22.4) version: 0.2.5-2a777a1(zod@3.22.4)
'@rollup/plugin-commonjs': '@rollup/plugin-commonjs':
specifier: ^25.0.0 specifier: ^25.0.0
version: 25.0.7(rollup@4.12.0) version: 25.0.7(rollup@4.12.0)
@ -2715,8 +2715,8 @@ importers:
specifier: workspace:^1.4.0 specifier: workspace:^1.4.0
version: link:../app-insights version: link:../app-insights
'@logto/cloud': '@logto/cloud':
specifier: 0.2.5-81f06ea specifier: 0.2.5-2a777a1
version: 0.2.5-81f06ea(zod@3.22.4) version: 0.2.5-2a777a1(zod@3.22.4)
'@logto/connector-kit': '@logto/connector-kit':
specifier: workspace:^2.1.0 specifier: workspace:^2.1.0
version: link:../toolkit/connector-kit version: link:../toolkit/connector-kit
@ -3202,8 +3202,8 @@ importers:
version: 3.22.4 version: 3.22.4
devDependencies: devDependencies:
'@logto/cloud': '@logto/cloud':
specifier: 0.2.5-81f06ea specifier: 0.2.5-2a777a1
version: 0.2.5-81f06ea(zod@3.22.4) version: 0.2.5-2a777a1(zod@3.22.4)
'@silverhand/eslint-config': '@silverhand/eslint-config':
specifier: 5.0.0 specifier: 5.0.0
version: 5.0.0(eslint@8.44.0)(prettier@3.0.0)(typescript@5.3.3) 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 jose: 5.2.2
dev: true dev: true
/@logto/cloud@0.2.5-81f06ea(zod@3.22.4): /@logto/cloud@0.2.5-2a777a1(zod@3.22.4):
resolution: {integrity: sha512-7u2VY8qlRoaheWDEbHdoFmQP9MbloKuuCwbz1jk+Wrn2EE1v+tgixVK/MiyFaAN5mLAVLAlCVQ00JIabw+g6YA==} resolution: {integrity: sha512-RnU13Hrv5phYtIjVHDo0Ik1ZFvEOT5XBdQ0fDOHBFuGH+1Xd4X4HK79Mm5iC5JMM7KxxuH7bb6lStCvsOkUUYw==}
engines: {node: ^20.9.0} engines: {node: ^20.9.0}
dependencies: dependencies:
'@silverhand/essentials': 2.9.0 '@silverhand/essentials': 2.9.0
@ -18009,6 +18009,9 @@ packages:
resolution: {integrity: sha512-2GTVocFkwblV/TIg9AmT7TI2fO4xdWkyN8aFUEVtiVNWt96GTR3FgQyHFValfCbcj1k9Xf962Ws2hYXYUr9k1Q==} resolution: {integrity: sha512-2GTVocFkwblV/TIg9AmT7TI2fO4xdWkyN8aFUEVtiVNWt96GTR3FgQyHFValfCbcj1k9Xf962Ws2hYXYUr9k1Q==}
engines: {node: '>= 12.0.0'} engines: {node: '>= 12.0.0'}
hasBin: true hasBin: true
peerDependenciesMeta:
'@parcel/core':
optional: true
dependencies: dependencies:
'@parcel/config-default': 2.9.3(@parcel/core@2.9.3)(postcss@8.4.31) '@parcel/config-default': 2.9.3(@parcel/core@2.9.3)(postcss@8.4.31)
'@parcel/core': 2.9.3 '@parcel/core': 2.9.3