mirror of
https://github.com/logto-io/logto.git
synced 2024-12-16 20:26:19 -05:00
refactor(core,schemas): make the jwt customizer script field mandatory (#5696)
* refactor(core,schemas): make the jwt customizer script field mandatory make the jwt customizer script field mandatory * fix(schemas): fix the alteration script fix the alteration script * fix(schemas): fix ut fix ut
This commit is contained in:
parent
c08feeb872
commit
559331d51e
5 changed files with 69 additions and 38 deletions
|
@ -155,11 +155,10 @@ describe('configs JWT customizer routes', () => {
|
|||
jest.spyOn(mockCloudClient, 'post').mockResolvedValue(cloudConnectionResponse);
|
||||
|
||||
const payload: JwtCustomizerTestRequestBody = {
|
||||
tokenType: LogtoJwtTokenKeyType.AccessToken,
|
||||
tokenType: LogtoJwtTokenKeyType.ClientCredentials,
|
||||
script: mockJwtCustomizerConfigForClientCredentials.value.script,
|
||||
environmentVariables: mockJwtCustomizerConfigForClientCredentials.value.environmentVariables,
|
||||
token: {},
|
||||
script: mockJwtCustomizerConfigForAccessToken.value.script,
|
||||
environmentVariables: mockJwtCustomizerConfigForAccessToken.value.environmentVariables,
|
||||
context: mockJwtCustomizerConfigForAccessToken.value.contextSample,
|
||||
};
|
||||
|
||||
const response = await routeRequester.post('/configs/jwt-customizer/test').send(payload);
|
||||
|
@ -167,7 +166,7 @@ describe('configs JWT customizer routes', () => {
|
|||
expect(mockLogtoConfigsLibrary.deployJwtCustomizerScript).toHaveBeenCalledWith(
|
||||
tenantContext.cloudConnection,
|
||||
{
|
||||
key: LogtoJwtTokenKey.AccessToken,
|
||||
key: LogtoJwtTokenKey.ClientCredentials,
|
||||
value: payload,
|
||||
isTest: true,
|
||||
}
|
||||
|
|
|
@ -207,11 +207,6 @@ export default function logtoConfigJwtCustomizerRoutes<T extends AuthedRouter>(
|
|||
router.post(
|
||||
'/configs/jwt-customizer/test',
|
||||
koaGuard({
|
||||
/**
|
||||
* Early throws when:
|
||||
* 1. no `script` provided.
|
||||
* 2. no `tokenSample` provided.
|
||||
*/
|
||||
body: jwtCustomizerTestRequestBodyGuard,
|
||||
response: jsonObjectGuard,
|
||||
status: [200, 400, 403, 422],
|
||||
|
|
|
@ -0,0 +1,23 @@
|
|||
import { sql } from '@silverhand/slonik';
|
||||
|
||||
import type { AlterationScript } from '../lib/types/alteration.js';
|
||||
|
||||
const alteration: AlterationScript = {
|
||||
// We are making the jwt-customizer script field mandatory
|
||||
// Delete the records in logto_configs where key is jwt.accessToken or jwt.clientCredentials and value jsonb's script field is undefined
|
||||
up: async (pool) => {
|
||||
await pool.query(
|
||||
sql`
|
||||
delete from logto_configs
|
||||
where key in ('jwt.accessToken', 'jwt.clientCredentials')
|
||||
and value->>'script' is null
|
||||
`
|
||||
);
|
||||
},
|
||||
down: async () => {
|
||||
// No down script available, this is a non-reversible operation
|
||||
// It is fine since we have not released this feature yet
|
||||
},
|
||||
};
|
||||
|
||||
export default alteration;
|
|
@ -1,5 +1,5 @@
|
|||
import { pick } from '@silverhand/essentials';
|
||||
import { describe, it, expect } from 'vitest';
|
||||
import { describe, expect, it } from 'vitest';
|
||||
|
||||
import {
|
||||
accessTokenJwtCustomizerGuard,
|
||||
|
@ -7,6 +7,8 @@ import {
|
|||
} from './jwt-customizer.js';
|
||||
|
||||
const allFields = ['script', 'environmentVariables', 'contextSample', 'tokenSample'] as const;
|
||||
const requiredFields = ['script'] as const;
|
||||
const optionalFields = ['environmentVariables', 'contextSample', 'tokenSample'] as const;
|
||||
|
||||
const testClientCredentialsTokenPayload = {
|
||||
script: '',
|
||||
|
@ -39,14 +41,35 @@ const testAccessTokenPayload = {
|
|||
};
|
||||
|
||||
describe('test token sample guard', () => {
|
||||
it.each(allFields)('should pass guard with any of the field not specified', (droppedField) => {
|
||||
it.each(optionalFields)(
|
||||
'should pass guard with any of the optionalFields not specified',
|
||||
(droppedField) => {
|
||||
const resultAccessToken = accessTokenJwtCustomizerGuard.safeParse(
|
||||
pick(testAccessTokenPayload, ...allFields.filter((field) => field !== droppedField))
|
||||
);
|
||||
if (!resultAccessToken.success) {
|
||||
console.log('resultAccessToken.error', resultAccessToken.error);
|
||||
}
|
||||
expect(resultAccessToken.success).toBe(true);
|
||||
|
||||
const resultClientCredentials = clientCredentialsJwtCustomizerGuard.safeParse(
|
||||
pick(
|
||||
testClientCredentialsTokenPayload,
|
||||
...allFields.filter((field) => field !== droppedField)
|
||||
)
|
||||
);
|
||||
if (!resultClientCredentials.success) {
|
||||
console.log('resultClientCredentials.error', resultClientCredentials.error);
|
||||
}
|
||||
expect(resultClientCredentials.success).toBe(true);
|
||||
}
|
||||
);
|
||||
|
||||
it.each(requiredFields)('should throw when required field is not specified', (droppedField) => {
|
||||
const resultAccessToken = accessTokenJwtCustomizerGuard.safeParse(
|
||||
pick(testAccessTokenPayload, ...allFields.filter((field) => field !== droppedField))
|
||||
);
|
||||
if (!resultAccessToken.success) {
|
||||
console.log('resultAccessToken.error', resultAccessToken.error);
|
||||
}
|
||||
expect(resultAccessToken.success).toBe(true);
|
||||
expect(resultAccessToken.success).toBe(false);
|
||||
|
||||
const resultClientCredentials = clientCredentialsJwtCustomizerGuard.safeParse(
|
||||
pick(
|
||||
|
@ -54,10 +77,7 @@ describe('test token sample guard', () => {
|
|||
...allFields.filter((field) => field !== droppedField)
|
||||
)
|
||||
);
|
||||
if (!resultClientCredentials.success) {
|
||||
console.log('resultClientCredentials.error', resultClientCredentials.error);
|
||||
}
|
||||
expect(resultClientCredentials.success).toBe(true);
|
||||
expect(resultClientCredentials.success).toBe(false);
|
||||
});
|
||||
|
||||
it.each(allFields)(
|
||||
|
|
|
@ -1,6 +1,6 @@
|
|||
import { z } from 'zod';
|
||||
|
||||
import { Roles, UserSsoIdentities, Organizations } from '../../db-entries/index.js';
|
||||
import { Organizations, Roles, UserSsoIdentities } from '../../db-entries/index.js';
|
||||
import { jsonObjectGuard, mfaFactorsGuard } from '../../foundations/index.js';
|
||||
import { scopeResponseGuard } from '../scope.js';
|
||||
import { userInfoGuard } from '../user.js';
|
||||
|
@ -32,13 +32,11 @@ export const jwtCustomizerUserContextGuard = userInfoGuard.extend({
|
|||
|
||||
export type JwtCustomizerUserContext = z.infer<typeof jwtCustomizerUserContextGuard>;
|
||||
|
||||
export const jwtCustomizerGuard = z
|
||||
.object({
|
||||
script: z.string(),
|
||||
environmentVariables: z.record(z.string()),
|
||||
contextSample: jsonObjectGuard,
|
||||
})
|
||||
.partial();
|
||||
export const jwtCustomizerGuard = z.object({
|
||||
script: z.string(),
|
||||
environmentVariables: z.record(z.string()).optional(),
|
||||
contextSample: jsonObjectGuard.optional(),
|
||||
});
|
||||
|
||||
export const accessTokenJwtCustomizerGuard = jwtCustomizerGuard
|
||||
.extend({
|
||||
|
@ -66,25 +64,21 @@ export enum LogtoJwtTokenKeyType {
|
|||
|
||||
/**
|
||||
* This guard is for the core JWT customizer testing API request body guard.
|
||||
* Unlike the DB guard
|
||||
*
|
||||
* - rename the `tokenSample` to `token` and is required for testing.
|
||||
* - rename the `contextSample` to `context` and is required for AccessToken testing.
|
||||
*/
|
||||
export const jwtCustomizerTestRequestBodyGuard = z.discriminatedUnion('tokenType', [
|
||||
z.object({
|
||||
tokenType: z.literal(LogtoJwtTokenKeyType.AccessToken),
|
||||
...accessTokenJwtCustomizerGuard
|
||||
.required({
|
||||
script: true,
|
||||
})
|
||||
.pick({ environmentVariables: true, script: true }).shape,
|
||||
...accessTokenJwtCustomizerGuard.pick({ environmentVariables: true, script: true }).shape,
|
||||
token: accessTokenJwtCustomizerGuard.required().shape.tokenSample,
|
||||
context: accessTokenJwtCustomizerGuard.required().shape.contextSample,
|
||||
}),
|
||||
z.object({
|
||||
tokenType: z.literal(LogtoJwtTokenKeyType.ClientCredentials),
|
||||
...clientCredentialsJwtCustomizerGuard
|
||||
.required({
|
||||
script: true,
|
||||
})
|
||||
.pick({ environmentVariables: true, script: true }).shape,
|
||||
...clientCredentialsJwtCustomizerGuard.pick({ environmentVariables: true, script: true }).shape,
|
||||
token: clientCredentialsJwtCustomizerGuard.required().shape.tokenSample,
|
||||
}),
|
||||
]);
|
||||
|
|
Loading…
Reference in a new issue