0
Fork 0
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:
Gao Sun 2024-04-11 15:16:53 +08:00 committed by GitHub
parent 07ed139d6a
commit b3740656f5
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
12 changed files with 67 additions and 44 deletions

View file

@ -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",

View file

@ -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');

View file

@ -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,
})); }));

View file

@ -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
); );
} }

View file

@ -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,
}); });
}); });
}); });

View file

@ -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,
}); });
}; };

View file

@ -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,

View file

@ -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>]>) {

View file

@ -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,

View 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',
});

View file

@ -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';

View file

@ -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