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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

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 './phone.js';
export * from './sub-domain.js';
export * from './fetch.js';

View file

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