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