mirror of
https://github.com/logto-io/logto.git
synced 2024-12-16 20:26:19 -05:00
refactor(core): partially remove got (#5596)
* refactor(core): partially remove got * refactor: use shared form-urlencoded headers
This commit is contained in:
parent
07ed139d6a
commit
b3740656f5
12 changed files with 67 additions and 44 deletions
|
@ -44,6 +44,7 @@
|
||||||
"@logto/schemas": "workspace:^1.15.0",
|
"@logto/schemas": "workspace:^1.15.0",
|
||||||
"@logto/shared": "workspace:^3.1.0",
|
"@logto/shared": "workspace:^3.1.0",
|
||||||
"@silverhand/essentials": "^2.9.0",
|
"@silverhand/essentials": "^2.9.0",
|
||||||
|
"@silverhand/slonik": "31.0.0-beta.2",
|
||||||
"@simplewebauthn/server": "^9.0.0",
|
"@simplewebauthn/server": "^9.0.0",
|
||||||
"@withtyped/client": "^0.8.4",
|
"@withtyped/client": "^0.8.4",
|
||||||
"camelcase": "^8.0.0",
|
"camelcase": "^8.0.0",
|
||||||
|
@ -72,6 +73,7 @@
|
||||||
"koa-proxies": "^0.12.1",
|
"koa-proxies": "^0.12.1",
|
||||||
"koa-router": "^12.0.0",
|
"koa-router": "^12.0.0",
|
||||||
"koa-send": "^5.0.1",
|
"koa-send": "^5.0.1",
|
||||||
|
"ky": "^1.2.3",
|
||||||
"lru-cache": "^10.0.0",
|
"lru-cache": "^10.0.0",
|
||||||
"nanoid": "^5.0.1",
|
"nanoid": "^5.0.1",
|
||||||
"oidc-provider": "^8.2.2",
|
"oidc-provider": "^8.2.2",
|
||||||
|
@ -85,7 +87,6 @@
|
||||||
"roarr": "^7.11.0",
|
"roarr": "^7.11.0",
|
||||||
"samlify": "2.8.11",
|
"samlify": "2.8.11",
|
||||||
"semver": "^7.3.8",
|
"semver": "^7.3.8",
|
||||||
"@silverhand/slonik": "31.0.0-beta.2",
|
|
||||||
"snake-case": "^4.0.0",
|
"snake-case": "^4.0.0",
|
||||||
"snakecase-keys": "^8.0.0",
|
"snakecase-keys": "^8.0.0",
|
||||||
"zod": "^3.22.4"
|
"zod": "^3.22.4"
|
||||||
|
@ -116,7 +117,7 @@
|
||||||
"jest-matcher-specific-error": "^1.0.0",
|
"jest-matcher-specific-error": "^1.0.0",
|
||||||
"jsonc-eslint-parser": "^2.4.0",
|
"jsonc-eslint-parser": "^2.4.0",
|
||||||
"lint-staged": "^15.0.0",
|
"lint-staged": "^15.0.0",
|
||||||
"nock": "^13.3.1",
|
"nock": "^14.0.0-beta.5",
|
||||||
"node-mocks-http": "^1.12.1",
|
"node-mocks-http": "^1.12.1",
|
||||||
"nodemon": "^3.0.0",
|
"nodemon": "^3.0.0",
|
||||||
"prettier": "^3.0.0",
|
"prettier": "^3.0.0",
|
||||||
|
|
|
@ -1,8 +1,9 @@
|
||||||
import type router from '@logto/cloud/routes';
|
import type router from '@logto/cloud/routes';
|
||||||
import { cloudConnectionDataGuard, CloudScope } from '@logto/schemas';
|
import { cloudConnectionDataGuard, CloudScope } from '@logto/schemas';
|
||||||
|
import { formUrlEncodedHeaders } from '@logto/shared';
|
||||||
import { appendPath } from '@silverhand/essentials';
|
import { appendPath } from '@silverhand/essentials';
|
||||||
import Client from '@withtyped/client';
|
import Client from '@withtyped/client';
|
||||||
import { got } from 'got';
|
import ky from 'ky';
|
||||||
import { z } from 'zod';
|
import { z } from 'zod';
|
||||||
|
|
||||||
import { EnvSet } from '#src/env-set/index.js';
|
import { EnvSet } from '#src/env-set/index.js';
|
||||||
|
@ -71,20 +72,21 @@ export class CloudConnectionLibrary {
|
||||||
|
|
||||||
const { tokenEndpoint, appId, appSecret, resource } = await this.getCloudConnectionData();
|
const { tokenEndpoint, appId, appSecret, resource } = await this.getCloudConnectionData();
|
||||||
|
|
||||||
const httpResponse = await got.post({
|
const text = await ky
|
||||||
url: tokenEndpoint,
|
.post(tokenEndpoint, {
|
||||||
headers: {
|
headers: {
|
||||||
'Content-Type': 'application/x-www-form-urlencoded',
|
...formUrlEncodedHeaders,
|
||||||
Authorization: `Basic ${Buffer.from(`${appId}:${appSecret}`).toString('base64')}`,
|
Authorization: `Basic ${Buffer.from(`${appId}:${appSecret}`).toString('base64')}`,
|
||||||
},
|
},
|
||||||
form: {
|
body: new URLSearchParams({
|
||||||
grant_type: 'client_credentials',
|
grant_type: 'client_credentials',
|
||||||
resource,
|
resource,
|
||||||
scope: scopes.join(' '),
|
scope: scopes.join(' '),
|
||||||
},
|
}),
|
||||||
});
|
})
|
||||||
|
.text();
|
||||||
|
|
||||||
const result = accessTokenResponseGuard.safeParse(safeParseJson(httpResponse.body));
|
const result = accessTokenResponseGuard.safeParse(safeParseJson(text));
|
||||||
|
|
||||||
if (!result.success) {
|
if (!result.success) {
|
||||||
throw new Error('Unable to get access token for Cloud service');
|
throw new Error('Unable to get access token for Cloud service');
|
||||||
|
|
|
@ -18,7 +18,9 @@ mockEsm('#src/utils/sign.js', () => ({
|
||||||
}));
|
}));
|
||||||
|
|
||||||
const { sendWebhookRequest } = mockEsm('./utils.js', () => ({
|
const { sendWebhookRequest } = mockEsm('./utils.js', () => ({
|
||||||
sendWebhookRequest: jest.fn().mockResolvedValue({ statusCode: 200, body: '{"message":"ok"}' }),
|
sendWebhookRequest: jest
|
||||||
|
.fn()
|
||||||
|
.mockResolvedValue({ status: 200, text: async () => '{"message":"ok"}' }),
|
||||||
generateHookTestPayload,
|
generateHookTestPayload,
|
||||||
parseResponse,
|
parseResponse,
|
||||||
}));
|
}));
|
||||||
|
|
|
@ -9,7 +9,7 @@ import {
|
||||||
} from '@logto/schemas';
|
} from '@logto/schemas';
|
||||||
import { generateStandardId } from '@logto/shared';
|
import { generateStandardId } from '@logto/shared';
|
||||||
import { conditional, pick, trySafe } from '@silverhand/essentials';
|
import { conditional, pick, trySafe } from '@silverhand/essentials';
|
||||||
import { HTTPError } from 'got';
|
import { HTTPError } from 'ky';
|
||||||
|
|
||||||
import RequestError from '#src/errors/RequestError/index.js';
|
import RequestError from '#src/errors/RequestError/index.js';
|
||||||
import { LogEntry } from '#src/middleware/koa-audit-log.js';
|
import { LogEntry } from '#src/middleware/koa-audit-log.js';
|
||||||
|
@ -106,13 +106,15 @@ export const createHookLibrary = (queries: Queries) => {
|
||||||
})
|
})
|
||||||
.then(async (response) => {
|
.then(async (response) => {
|
||||||
logEntry.append({
|
logEntry.append({
|
||||||
response: parseResponse(response),
|
response: await parseResponse(response),
|
||||||
});
|
});
|
||||||
})
|
})
|
||||||
.catch(async (error) => {
|
.catch(async (error) => {
|
||||||
logEntry.append({
|
logEntry.append({
|
||||||
result: LogResult.Error,
|
result: LogResult.Error,
|
||||||
response: conditional(error instanceof HTTPError && parseResponse(error.response)),
|
response: conditional(
|
||||||
|
error instanceof HTTPError && (await parseResponse(error.response))
|
||||||
|
),
|
||||||
error: conditional(error instanceof Error && String(error)),
|
error: conditional(error instanceof Error && String(error)),
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
@ -151,8 +153,8 @@ export const createHookLibrary = (queries: Queries) => {
|
||||||
code: 'hook.endpoint_responded_with_error',
|
code: 'hook.endpoint_responded_with_error',
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
responseStatus: error.response.statusCode,
|
responseStatus: error.response.status,
|
||||||
responseBody: String(error.response.rawBody),
|
responseBody: await error.response.text(),
|
||||||
} satisfies HookTestErrorResponseData
|
} satisfies HookTestErrorResponseData
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,13 +1,13 @@
|
||||||
import { HookEvent } from '@logto/schemas';
|
import { HookEvent } from '@logto/schemas';
|
||||||
import { createMockUtils } from '@logto/shared/esm';
|
import { createMockUtils } from '@logto/shared/esm';
|
||||||
import { got } from 'got';
|
import ky from 'ky';
|
||||||
|
|
||||||
const { jest } = import.meta;
|
const { jest } = import.meta;
|
||||||
|
|
||||||
const { mockEsm, mockEsmWithActual } = createMockUtils(jest);
|
const { mockEsm, mockEsmWithActual } = createMockUtils(jest);
|
||||||
|
|
||||||
const post = jest
|
const post = jest
|
||||||
.spyOn(got, 'post')
|
.spyOn(ky, 'post')
|
||||||
// @ts-expect-error
|
// @ts-expect-error
|
||||||
.mockImplementation(jest.fn(async () => ({ statusCode: 200, body: '{"message":"ok"}' })));
|
.mockImplementation(jest.fn(async () => ({ statusCode: 200, body: '{"message":"ok"}' })));
|
||||||
|
|
||||||
|
@ -44,7 +44,7 @@ describe('sendWebhookRequest', () => {
|
||||||
},
|
},
|
||||||
json: testPayload,
|
json: testPayload,
|
||||||
retry: { limit: 3 },
|
retry: { limit: 3 },
|
||||||
timeout: { request: 10_000 },
|
timeout: 10_000,
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
|
@ -5,15 +5,18 @@ import {
|
||||||
type HookConfig,
|
type HookConfig,
|
||||||
} from '@logto/schemas';
|
} from '@logto/schemas';
|
||||||
import { conditional, trySafe } from '@silverhand/essentials';
|
import { conditional, trySafe } from '@silverhand/essentials';
|
||||||
import { got, type Response } from 'got';
|
import ky, { type KyResponse } from 'ky';
|
||||||
|
|
||||||
import { sign } from '#src/utils/sign.js';
|
import { sign } from '#src/utils/sign.js';
|
||||||
|
|
||||||
export const parseResponse = ({ statusCode, body }: Response) => ({
|
export const parseResponse = async (response: KyResponse) => {
|
||||||
statusCode,
|
const body = await response.text();
|
||||||
// eslint-disable-next-line no-restricted-syntax
|
return {
|
||||||
body: trySafe(() => JSON.parse(String(body)) as unknown) ?? String(body),
|
statusCode: response.status,
|
||||||
});
|
// eslint-disable-next-line no-restricted-syntax
|
||||||
|
body: trySafe(() => JSON.parse(body) as unknown) ?? String(body),
|
||||||
|
};
|
||||||
|
};
|
||||||
|
|
||||||
type SendWebhookRequest = {
|
type SendWebhookRequest = {
|
||||||
hookConfig: HookConfig;
|
hookConfig: HookConfig;
|
||||||
|
@ -28,7 +31,7 @@ export const sendWebhookRequest = async ({
|
||||||
}: SendWebhookRequest) => {
|
}: SendWebhookRequest) => {
|
||||||
const { url, headers, retries } = hookConfig;
|
const { url, headers, retries } = hookConfig;
|
||||||
|
|
||||||
return got.post(url, {
|
return ky.post(url, {
|
||||||
headers: {
|
headers: {
|
||||||
'user-agent': 'Logto (https://logto.io/)',
|
'user-agent': 'Logto (https://logto.io/)',
|
||||||
...headers,
|
...headers,
|
||||||
|
@ -36,7 +39,7 @@ export const sendWebhookRequest = async ({
|
||||||
},
|
},
|
||||||
json: payload,
|
json: payload,
|
||||||
retry: { limit: retries ?? 3 },
|
retry: { limit: retries ?? 3 },
|
||||||
timeout: { request: 10_000 },
|
timeout: 10_000,
|
||||||
});
|
});
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
|
@ -6,6 +6,7 @@ import {
|
||||||
type Role,
|
type Role,
|
||||||
type ProtectedAppMetadata,
|
type ProtectedAppMetadata,
|
||||||
} from '@logto/schemas';
|
} from '@logto/schemas';
|
||||||
|
import { formUrlEncodedHeaders } from '@logto/shared';
|
||||||
import { conditional } from '@silverhand/essentials';
|
import { conditional } from '@silverhand/essentials';
|
||||||
|
|
||||||
import { authedAdminApi, oidcApi } from './api.js';
|
import { authedAdminApi, oidcApi } from './api.js';
|
||||||
|
@ -99,9 +100,7 @@ export const generateM2mLog = async (applicationId: string) => {
|
||||||
|
|
||||||
// This is a token request with insufficient parameters and should fail. We make the request to generate a log for the current machine to machine app.
|
// This is a token request with insufficient parameters and should fail. We make the request to generate a log for the current machine to machine app.
|
||||||
return oidcApi.post('token', {
|
return oidcApi.post('token', {
|
||||||
headers: {
|
headers: formUrlEncodedHeaders,
|
||||||
'Content-Type': 'application/x-www-form-urlencoded',
|
|
||||||
},
|
|
||||||
body: new URLSearchParams({
|
body: new URLSearchParams({
|
||||||
client_id: id,
|
client_id: id,
|
||||||
client_secret: secret,
|
client_secret: secret,
|
||||||
|
|
|
@ -95,7 +95,7 @@ describe('trigger hooks', () => {
|
||||||
expect(
|
expect(
|
||||||
logs.some(
|
logs.some(
|
||||||
({ payload: { result, error } }) =>
|
({ payload: { result, error } }) =>
|
||||||
result === LogResult.Error && error === 'RequestError: Invalid URL'
|
result === LogResult.Error && error === 'TypeError: Failed to parse URL from not_work_url'
|
||||||
)
|
)
|
||||||
).toBeTruthy();
|
).toBeTruthy();
|
||||||
|
|
||||||
|
@ -137,7 +137,7 @@ describe('trigger hooks', () => {
|
||||||
|
|
||||||
// Check hook trigger log
|
// Check hook trigger log
|
||||||
for (const [hook, expectedResult, expectedError] of [
|
for (const [hook, expectedResult, expectedError] of [
|
||||||
[hook1, LogResult.Error, 'RequestError: Invalid URL'],
|
[hook1, LogResult.Error, 'TypeError: Failed to parse URL from not_work_url'],
|
||||||
[hook2, LogResult.Success, undefined],
|
[hook2, LogResult.Success, undefined],
|
||||||
[hook3, LogResult.Success, undefined],
|
[hook3, LogResult.Success, undefined],
|
||||||
] satisfies Array<[Hook, LogResult, Optional<string>]>) {
|
] satisfies Array<[Hook, LogResult, Optional<string>]>) {
|
||||||
|
|
|
@ -3,6 +3,7 @@ import assert from 'node:assert';
|
||||||
import { decodeAccessToken } from '@logto/js';
|
import { decodeAccessToken } from '@logto/js';
|
||||||
import { type LogtoConfig, Prompt, PersistKey } from '@logto/node';
|
import { type LogtoConfig, Prompt, PersistKey } from '@logto/node';
|
||||||
import { GrantType, InteractionEvent, demoAppApplicationId } from '@logto/schemas';
|
import { GrantType, InteractionEvent, demoAppApplicationId } from '@logto/schemas';
|
||||||
|
import { formUrlEncodedHeaders } from '@logto/shared';
|
||||||
import { isKeyInObject, removeUndefinedKeys } from '@silverhand/essentials';
|
import { isKeyInObject, removeUndefinedKeys } from '@silverhand/essentials';
|
||||||
import { createRemoteJWKSet, jwtVerify } from 'jose';
|
import { createRemoteJWKSet, jwtVerify } from 'jose';
|
||||||
import ky, { HTTPError } from 'ky';
|
import ky, { HTTPError } from 'ky';
|
||||||
|
@ -51,9 +52,7 @@ class MockOrganizationClient extends MockClient {
|
||||||
try {
|
try {
|
||||||
const json = await ky
|
const json = await ky
|
||||||
.post(`${this.config.endpoint}/oidc/token`, {
|
.post(`${this.config.endpoint}/oidc/token`, {
|
||||||
headers: {
|
headers: formUrlEncodedHeaders,
|
||||||
'Content-Type': 'application/x-www-form-urlencoded',
|
|
||||||
},
|
|
||||||
body: new URLSearchParams(
|
body: new URLSearchParams(
|
||||||
removeUndefinedKeys({
|
removeUndefinedKeys({
|
||||||
grant_type: GrantType.RefreshToken,
|
grant_type: GrantType.RefreshToken,
|
||||||
|
|
4
packages/shared/src/utils/fetch.ts
Normal file
4
packages/shared/src/utils/fetch.ts
Normal file
|
@ -0,0 +1,4 @@
|
||||||
|
/** The necessary headers for sending a form-urlencoded request. */
|
||||||
|
export const formUrlEncodedHeaders = Object.freeze({
|
||||||
|
'content-type': 'application/x-www-form-urlencoded',
|
||||||
|
});
|
|
@ -4,3 +4,4 @@ export * from './id.js';
|
||||||
export * from './user-display-name.js';
|
export * from './user-display-name.js';
|
||||||
export * from './phone.js';
|
export * from './phone.js';
|
||||||
export * from './sub-domain.js';
|
export * from './sub-domain.js';
|
||||||
|
export * from './fetch.js';
|
||||||
|
|
|
@ -3152,6 +3152,9 @@ importers:
|
||||||
koa-send:
|
koa-send:
|
||||||
specifier: ^5.0.1
|
specifier: ^5.0.1
|
||||||
version: 5.0.1
|
version: 5.0.1
|
||||||
|
ky:
|
||||||
|
specifier: ^1.2.3
|
||||||
|
version: 1.2.3
|
||||||
lru-cache:
|
lru-cache:
|
||||||
specifier: ^10.0.0
|
specifier: ^10.0.0
|
||||||
version: 10.0.0
|
version: 10.0.0
|
||||||
|
@ -3277,8 +3280,8 @@ importers:
|
||||||
specifier: ^15.0.0
|
specifier: ^15.0.0
|
||||||
version: 15.0.2
|
version: 15.0.2
|
||||||
nock:
|
nock:
|
||||||
specifier: ^13.3.1
|
specifier: ^14.0.0-beta.5
|
||||||
version: 13.3.1
|
version: 14.0.0-beta.5
|
||||||
node-mocks-http:
|
node-mocks-http:
|
||||||
specifier: ^1.12.1
|
specifier: ^1.12.1
|
||||||
version: 1.12.1
|
version: 1.12.1
|
||||||
|
@ -16216,7 +16219,6 @@ packages:
|
||||||
/ky@1.2.3:
|
/ky@1.2.3:
|
||||||
resolution: {integrity: sha512-2IM3VssHfG2zYz2FsHRUqIp8chhLc9uxDMcK2THxgFfv8pQhnMfN8L0ul+iW4RdBl5AglF8ooPIflRm3yNH0IA==}
|
resolution: {integrity: sha512-2IM3VssHfG2zYz2FsHRUqIp8chhLc9uxDMcK2THxgFfv8pQhnMfN8L0ul+iW4RdBl5AglF8ooPIflRm3yNH0IA==}
|
||||||
engines: {node: '>=18'}
|
engines: {node: '>=18'}
|
||||||
dev: true
|
|
||||||
|
|
||||||
/language-subtag-registry@0.3.22:
|
/language-subtag-registry@0.3.22:
|
||||||
resolution: {integrity: sha512-tN0MCzyWnoz/4nHS6uxdlFWoUZT7ABptwKPQ52Ea7URk6vll88bWBVhodtnlfEuCcKWNGoc+uGbw1cwa9IKh/w==}
|
resolution: {integrity: sha512-tN0MCzyWnoz/4nHS6uxdlFWoUZT7ABptwKPQ52Ea7URk6vll88bWBVhodtnlfEuCcKWNGoc+uGbw1cwa9IKh/w==}
|
||||||
|
@ -17435,6 +17437,14 @@ packages:
|
||||||
- supports-color
|
- supports-color
|
||||||
dev: true
|
dev: true
|
||||||
|
|
||||||
|
/nock@14.0.0-beta.5:
|
||||||
|
resolution: {integrity: sha512-u255tf4DYvyErTlPZA9uTfXghiZZy+NflUOFONPVKZ5tP0yaHwKig28zyFOLhu8y5YcCRC+V5vDk4HHileh2iw==}
|
||||||
|
engines: {node: '>= 18'}
|
||||||
|
dependencies:
|
||||||
|
json-stringify-safe: 5.0.1
|
||||||
|
propagate: 2.0.1
|
||||||
|
dev: true
|
||||||
|
|
||||||
/node-addon-api@3.2.1:
|
/node-addon-api@3.2.1:
|
||||||
resolution: {integrity: sha512-mmcei9JghVNDYydghQmeDX8KoAm0FAiYyIcUt/N4nhyAipB17pllZQDOJD2fotxABnt4Mdz+dKTO7eftLg4d0A==}
|
resolution: {integrity: sha512-mmcei9JghVNDYydghQmeDX8KoAm0FAiYyIcUt/N4nhyAipB17pllZQDOJD2fotxABnt4Mdz+dKTO7eftLg4d0A==}
|
||||||
dev: true
|
dev: true
|
||||||
|
|
Loading…
Reference in a new issue