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:
commit
a77fd3f97f
7 changed files with 100 additions and 44 deletions
|
@ -48,6 +48,6 @@
|
||||||
"access": "public"
|
"access": "public"
|
||||||
},
|
},
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
"@logto/cloud": "0.2.5-81f06ea"
|
"@logto/cloud": "0.2.5-2a777a1"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -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",
|
||||||
|
|
|
@ -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",
|
||||||
|
|
|
@ -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
|
||||||
|
|
|
@ -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) {
|
||||||
/**
|
/**
|
||||||
|
|
|
@ -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.
|
||||||
|
|
|
@ -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
|
||||||
|
|
Loading…
Reference in a new issue