@forklaunch/core 0.14.8 → 0.14.11-beta.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/lib/http/index.d.mts +126 -75
- package/lib/http/index.d.ts +126 -75
- package/lib/http/index.js +161 -93
- package/lib/http/index.js.map +1 -1
- package/lib/http/index.mjs +119 -49
- package/lib/http/index.mjs.map +1 -1
- package/package.json +9 -9
package/lib/http/index.mjs
CHANGED
@@ -81,11 +81,15 @@ function isTypedHandler(maybeTypedHandler) {
|
|
81
81
|
|
82
82
|
// src/http/middleware/request/auth.middleware.ts
|
83
83
|
import { isNever } from "@forklaunch/common";
|
84
|
+
import {
|
85
|
+
prettyPrintParseErrors
|
86
|
+
} from "@forklaunch/validator";
|
84
87
|
|
85
88
|
// src/http/discriminateAuthMethod.ts
|
86
89
|
import { jwtVerify } from "jose";
|
87
90
|
|
88
91
|
// src/http/createHmacToken.ts
|
92
|
+
import { safeStringify } from "@forklaunch/common";
|
89
93
|
import { createHmac } from "crypto";
|
90
94
|
function createHmacToken({
|
91
95
|
method,
|
@@ -96,11 +100,14 @@ function createHmacToken({
|
|
96
100
|
secretKey
|
97
101
|
}) {
|
98
102
|
const hmac = createHmac("sha256", secretKey);
|
99
|
-
|
103
|
+
const bodyString = body ? `${safeStringify(body)}
|
104
|
+
` : void 0;
|
105
|
+
hmac.update(
|
106
|
+
`${method}
|
100
107
|
${path}
|
101
|
-
${
|
102
|
-
${
|
103
|
-
|
108
|
+
${bodyString}${timestamp.toISOString()}
|
109
|
+
${nonce}`
|
110
|
+
);
|
104
111
|
return hmac.digest("base64");
|
105
112
|
}
|
106
113
|
|
@@ -120,6 +127,12 @@ function isJwtAuthMethod(maybeJwtAuthMethod) {
|
|
120
127
|
}
|
121
128
|
|
122
129
|
// src/http/discriminateAuthMethod.ts
|
130
|
+
var DEFAULT_TTL = 60 * 1e3 * 5;
|
131
|
+
var memoizedJwks = {
|
132
|
+
value: null,
|
133
|
+
lastUpdated: null,
|
134
|
+
ttl: DEFAULT_TTL
|
135
|
+
};
|
123
136
|
async function discriminateAuthMethod(auth) {
|
124
137
|
let authMethod;
|
125
138
|
if (isBasicAuthMethod(auth)) {
|
@@ -144,8 +157,17 @@ async function discriminateAuthMethod(auth) {
|
|
144
157
|
} else {
|
145
158
|
let jwks;
|
146
159
|
if ("jwksPublicKeyUrl" in jwt) {
|
147
|
-
|
148
|
-
|
160
|
+
if (memoizedJwks.value && memoizedJwks.lastUpdated && Date.now() - memoizedJwks.lastUpdated.getTime() < memoizedJwks.ttl) {
|
161
|
+
jwks = memoizedJwks.value;
|
162
|
+
} else {
|
163
|
+
const jwksResponse = await fetch(jwt.jwksPublicKeyUrl);
|
164
|
+
jwks = (await jwksResponse.json()).keys;
|
165
|
+
memoizedJwks.value = jwks;
|
166
|
+
memoizedJwks.lastUpdated = /* @__PURE__ */ new Date();
|
167
|
+
memoizedJwks.ttl = parseInt(
|
168
|
+
jwksResponse.headers.get("cache-control")?.split("=")[1] ?? `${DEFAULT_TTL / 1e3}`
|
169
|
+
) * 1e3;
|
170
|
+
}
|
149
171
|
} else if ("jwksPublicKey" in jwt) {
|
150
172
|
jwks = [jwt.jwksPublicKey];
|
151
173
|
}
|
@@ -155,6 +177,9 @@ async function discriminateAuthMethod(auth) {
|
|
155
177
|
const { payload } = await jwtVerify(token, key);
|
156
178
|
return payload;
|
157
179
|
} catch {
|
180
|
+
memoizedJwks.value = null;
|
181
|
+
memoizedJwks.lastUpdated = null;
|
182
|
+
memoizedJwks.ttl = DEFAULT_TTL;
|
158
183
|
continue;
|
159
184
|
}
|
160
185
|
}
|
@@ -172,7 +197,15 @@ async function discriminateAuthMethod(auth) {
|
|
172
197
|
type: "hmac",
|
173
198
|
auth: {
|
174
199
|
secretKeys: auth.hmac.secretKeys,
|
175
|
-
verificationFunction: async (
|
200
|
+
verificationFunction: async ({
|
201
|
+
method,
|
202
|
+
path,
|
203
|
+
body,
|
204
|
+
timestamp,
|
205
|
+
nonce,
|
206
|
+
signature,
|
207
|
+
secretKey
|
208
|
+
}) => {
|
176
209
|
return createHmacToken({
|
177
210
|
method,
|
178
211
|
path,
|
@@ -250,7 +283,7 @@ function parseHmacTokenPart(part, expectedKey) {
|
|
250
283
|
if (key !== expectedKey || rest.length === 0) return void 0;
|
251
284
|
return rest.join("=");
|
252
285
|
}
|
253
|
-
async function checkAuthorizationToken(
|
286
|
+
async function checkAuthorizationToken(req, authorizationMethod, authorizationToken, globalOptions) {
|
254
287
|
if (authorizationMethod == null) {
|
255
288
|
return void 0;
|
256
289
|
}
|
@@ -265,7 +298,7 @@ async function checkAuthorizationToken(authorizationMethod, globalOptions, autho
|
|
265
298
|
if (!tokenParts.length || !tokenPrefix) {
|
266
299
|
return invalidAuthorizationTokenFormat;
|
267
300
|
}
|
268
|
-
let
|
301
|
+
let sessionPayload;
|
269
302
|
const { type, auth } = await discriminateAuthMethod(
|
270
303
|
collapsedAuthorizationMethod
|
271
304
|
);
|
@@ -285,19 +318,19 @@ async function checkAuthorizationToken(authorizationMethod, globalOptions, autho
|
|
285
318
|
if (!parsedKeyId || !parsedTimestamp || !parsedNonce || !parsedSignature) {
|
286
319
|
return invalidAuthorizationTokenFormat;
|
287
320
|
}
|
288
|
-
const verificationResult = await auth.verificationFunction(
|
289
|
-
req?.method ?? "",
|
290
|
-
req?.path ?? "",
|
291
|
-
|
292
|
-
parsedTimestamp,
|
293
|
-
parsedNonce,
|
294
|
-
parsedSignature,
|
295
|
-
collapsedAuthorizationMethod.hmac.secretKeys[parsedKeyId]
|
296
|
-
);
|
321
|
+
const verificationResult = await auth.verificationFunction({
|
322
|
+
method: req?.method ?? "",
|
323
|
+
path: req?.path ?? "",
|
324
|
+
body: req?.body,
|
325
|
+
timestamp: new Date(parsedTimestamp),
|
326
|
+
nonce: parsedNonce,
|
327
|
+
signature: parsedSignature,
|
328
|
+
secretKey: collapsedAuthorizationMethod.hmac.secretKeys[parsedKeyId]
|
329
|
+
});
|
297
330
|
if (!verificationResult) {
|
298
331
|
return invalidAuthorizationSignature;
|
299
332
|
}
|
300
|
-
|
333
|
+
sessionPayload = null;
|
301
334
|
break;
|
302
335
|
}
|
303
336
|
case "jwt": {
|
@@ -310,7 +343,7 @@ async function checkAuthorizationToken(authorizationMethod, globalOptions, autho
|
|
310
343
|
if (!decodedJwt) {
|
311
344
|
return invalidAuthorizationToken;
|
312
345
|
}
|
313
|
-
|
346
|
+
sessionPayload = decodedJwt;
|
314
347
|
} catch (error) {
|
315
348
|
req?.openTelemetryCollector.error(error);
|
316
349
|
return invalidAuthorizationToken;
|
@@ -323,7 +356,7 @@ async function checkAuthorizationToken(authorizationMethod, globalOptions, autho
|
|
323
356
|
return invalidAuthorizationTokenFormat;
|
324
357
|
}
|
325
358
|
if (auth.decodeResource) {
|
326
|
-
|
359
|
+
sessionPayload = await auth.decodeResource(token);
|
327
360
|
} else {
|
328
361
|
const [username, password] = Buffer.from(token, "base64").toString("utf-8").split(":");
|
329
362
|
if (!username || !password) {
|
@@ -332,7 +365,7 @@ async function checkAuthorizationToken(authorizationMethod, globalOptions, autho
|
|
332
365
|
if (!auth.login(username, password)) {
|
333
366
|
return invalidAuthorizationLogin;
|
334
367
|
}
|
335
|
-
|
368
|
+
sessionPayload = {
|
336
369
|
sub: username
|
337
370
|
};
|
338
371
|
}
|
@@ -342,16 +375,29 @@ async function checkAuthorizationToken(authorizationMethod, globalOptions, autho
|
|
342
375
|
isNever(type);
|
343
376
|
return [401, "Invalid Authorization method."];
|
344
377
|
}
|
345
|
-
if (isHmacMethod(collapsedAuthorizationMethod) &&
|
378
|
+
if (isHmacMethod(collapsedAuthorizationMethod) && sessionPayload == null) {
|
346
379
|
return;
|
347
380
|
}
|
348
|
-
if (
|
381
|
+
if (sessionPayload == null) {
|
349
382
|
return invalidAuthorizationToken;
|
350
383
|
}
|
384
|
+
req.session = sessionPayload;
|
385
|
+
if (collapsedAuthorizationMethod.sessionSchema) {
|
386
|
+
const parsedSession = req.schemaValidator.parse(
|
387
|
+
collapsedAuthorizationMethod.sessionSchema,
|
388
|
+
sessionPayload
|
389
|
+
);
|
390
|
+
if (!parsedSession.ok) {
|
391
|
+
return [
|
392
|
+
400,
|
393
|
+
`Invalid session: ${prettyPrintParseErrors(parsedSession.errors, "Session")}`
|
394
|
+
];
|
395
|
+
}
|
396
|
+
}
|
351
397
|
if (hasScopeChecks(collapsedAuthorizationMethod)) {
|
352
398
|
if (collapsedAuthorizationMethod.surfaceScopes) {
|
353
399
|
const resourceScopes = await collapsedAuthorizationMethod.surfaceScopes(
|
354
|
-
|
400
|
+
sessionPayload,
|
355
401
|
req
|
356
402
|
);
|
357
403
|
if (collapsedAuthorizationMethod.scopeHeirarchy) {
|
@@ -370,7 +416,7 @@ async function checkAuthorizationToken(authorizationMethod, globalOptions, autho
|
|
370
416
|
return [500, "No permission surfacing function provided."];
|
371
417
|
}
|
372
418
|
const resourcePermissions = await collapsedAuthorizationMethod.surfacePermissions(
|
373
|
-
|
419
|
+
sessionPayload,
|
374
420
|
req
|
375
421
|
);
|
376
422
|
if ("allowedPermissions" in collapsedAuthorizationMethod && collapsedAuthorizationMethod.allowedPermissions) {
|
@@ -392,7 +438,7 @@ async function checkAuthorizationToken(authorizationMethod, globalOptions, autho
|
|
392
438
|
return [500, "No role surfacing function provided."];
|
393
439
|
}
|
394
440
|
const resourceRoles = await collapsedAuthorizationMethod.surfaceRoles(
|
395
|
-
|
441
|
+
sessionPayload,
|
396
442
|
req
|
397
443
|
);
|
398
444
|
if ("allowedRoles" in collapsedAuthorizationMethod && collapsedAuthorizationMethod.allowedRoles) {
|
@@ -412,10 +458,10 @@ async function checkAuthorizationToken(authorizationMethod, globalOptions, autho
|
|
412
458
|
async function parseRequestAuth(req, res, next) {
|
413
459
|
const auth = req.contractDetails.auth;
|
414
460
|
const [error, message] = await checkAuthorizationToken(
|
461
|
+
req,
|
415
462
|
auth,
|
416
|
-
req._globalOptions?.()?.auth,
|
417
463
|
req.headers[auth?.headerName ?? "Authorization"] || req.headers[auth?.headerName ?? "authorization"],
|
418
|
-
req
|
464
|
+
req._globalOptions?.()?.auth
|
419
465
|
) ?? [];
|
420
466
|
if (error != null) {
|
421
467
|
res.type("text/plain");
|
@@ -794,7 +840,7 @@ function enrichDetails(path, contractDetails, requestSchema, responseSchemas, op
|
|
794
840
|
// src/http/middleware/request/parse.middleware.ts
|
795
841
|
import { isRecord } from "@forklaunch/common";
|
796
842
|
import {
|
797
|
-
prettyPrintParseErrors
|
843
|
+
prettyPrintParseErrors as prettyPrintParseErrors2
|
798
844
|
} from "@forklaunch/validator";
|
799
845
|
|
800
846
|
// src/http/guards/hasSend.ts
|
@@ -833,7 +879,7 @@ function parse(req, res, next) {
|
|
833
879
|
req.version = version;
|
834
880
|
res.version = req.version;
|
835
881
|
} else {
|
836
|
-
runningParseErrors +=
|
882
|
+
runningParseErrors += prettyPrintParseErrors2(
|
837
883
|
parsingResult.errors,
|
838
884
|
`Version ${version} request`
|
839
885
|
);
|
@@ -891,7 +937,7 @@ function parse(req, res, next) {
|
|
891
937
|
res.status(400);
|
892
938
|
if (hasSend(res)) {
|
893
939
|
res.send(
|
894
|
-
`${collectedParseErrors ??
|
940
|
+
`${collectedParseErrors ?? prettyPrintParseErrors2(parsedRequest.errors, "Request")}
|
895
941
|
|
896
942
|
Correlation id: ${req.context.correlationId ?? "No correlation ID"}`
|
897
943
|
);
|
@@ -901,7 +947,7 @@ Correlation id: ${req.context.correlationId ?? "No correlation ID"}`
|
|
901
947
|
return;
|
902
948
|
case "warning":
|
903
949
|
req.openTelemetryCollector.warn(
|
904
|
-
collectedParseErrors ??
|
950
|
+
collectedParseErrors ?? prettyPrintParseErrors2(parsedRequest.errors, "Request")
|
905
951
|
);
|
906
952
|
break;
|
907
953
|
case "none":
|
@@ -2919,7 +2965,7 @@ var getCodeForStatus = (status) => {
|
|
2919
2965
|
var httpStatusCodes_default = HTTPStatuses;
|
2920
2966
|
|
2921
2967
|
// src/http/mcpGenerator/mcpGenerator.ts
|
2922
|
-
import { isNever as isNever3, isRecord as isRecord3, safeStringify } from "@forklaunch/common";
|
2968
|
+
import { isNever as isNever3, isRecord as isRecord3, safeStringify as safeStringify2 } from "@forklaunch/common";
|
2923
2969
|
import { FastMCP } from "@forklaunch/fastmcp-fork";
|
2924
2970
|
import { string, ZodSchemaValidator } from "@forklaunch/validator/zod";
|
2925
2971
|
|
@@ -3056,7 +3102,7 @@ function generateMcpServer(schemaValidator, protocol, host, port, version, appli
|
|
3056
3102
|
if (discriminatedBody) {
|
3057
3103
|
switch (discriminatedBody.parserType) {
|
3058
3104
|
case "json": {
|
3059
|
-
parsedBody =
|
3105
|
+
parsedBody = safeStringify2(body);
|
3060
3106
|
break;
|
3061
3107
|
}
|
3062
3108
|
case "text": {
|
@@ -3088,7 +3134,7 @@ function generateMcpServer(schemaValidator, protocol, host, port, version, appli
|
|
3088
3134
|
parsedBody = new URLSearchParams(
|
3089
3135
|
Object.entries(body).map(([key, value]) => [
|
3090
3136
|
key,
|
3091
|
-
|
3137
|
+
safeStringify2(value)
|
3092
3138
|
])
|
3093
3139
|
);
|
3094
3140
|
} else {
|
@@ -3098,7 +3144,7 @@ function generateMcpServer(schemaValidator, protocol, host, port, version, appli
|
|
3098
3144
|
}
|
3099
3145
|
default: {
|
3100
3146
|
isNever3(discriminatedBody.parserType);
|
3101
|
-
parsedBody =
|
3147
|
+
parsedBody = safeStringify2(body);
|
3102
3148
|
break;
|
3103
3149
|
}
|
3104
3150
|
}
|
@@ -3107,7 +3153,7 @@ function generateMcpServer(schemaValidator, protocol, host, port, version, appli
|
|
3107
3153
|
const queryString = new URLSearchParams(
|
3108
3154
|
Object.entries(query).map(([key, value]) => [
|
3109
3155
|
key,
|
3110
|
-
|
3156
|
+
safeStringify2(value)
|
3111
3157
|
])
|
3112
3158
|
).toString();
|
3113
3159
|
url += queryString ? `?${queryString}` : "";
|
@@ -3140,7 +3186,7 @@ function generateMcpServer(schemaValidator, protocol, host, port, version, appli
|
|
3140
3186
|
content: [
|
3141
3187
|
{
|
3142
3188
|
type: "text",
|
3143
|
-
text:
|
3189
|
+
text: safeStringify2(await response.json())
|
3144
3190
|
}
|
3145
3191
|
]
|
3146
3192
|
};
|
@@ -3186,7 +3232,7 @@ function generateMcpServer(schemaValidator, protocol, host, port, version, appli
|
|
3186
3232
|
|
3187
3233
|
// src/http/middleware/response/parse.middleware.ts
|
3188
3234
|
import {
|
3189
|
-
prettyPrintParseErrors as
|
3235
|
+
prettyPrintParseErrors as prettyPrintParseErrors3
|
3190
3236
|
} from "@forklaunch/validator";
|
3191
3237
|
|
3192
3238
|
// src/http/guards/isResponseCompiledSchema.ts
|
@@ -3248,13 +3294,13 @@ function parse2(req, res, next) {
|
|
3248
3294
|
);
|
3249
3295
|
const parseErrors = [];
|
3250
3296
|
if (!parsedHeaders.ok) {
|
3251
|
-
const headerErrors =
|
3297
|
+
const headerErrors = prettyPrintParseErrors3(parsedHeaders.errors, "Header");
|
3252
3298
|
if (headerErrors) {
|
3253
3299
|
parseErrors.push(headerErrors);
|
3254
3300
|
}
|
3255
3301
|
}
|
3256
3302
|
if (!parsedResponse.ok) {
|
3257
|
-
const responseErrors =
|
3303
|
+
const responseErrors = prettyPrintParseErrors3(
|
3258
3304
|
parsedResponse.errors,
|
3259
3305
|
"Response"
|
3260
3306
|
);
|
@@ -3302,7 +3348,7 @@ import {
|
|
3302
3348
|
isNodeJsWriteableStream,
|
3303
3349
|
isRecord as isRecord4,
|
3304
3350
|
readableStreamToAsyncIterable,
|
3305
|
-
safeStringify as
|
3351
|
+
safeStringify as safeStringify3
|
3306
3352
|
} from "@forklaunch/common";
|
3307
3353
|
import { Readable, Transform } from "stream";
|
3308
3354
|
|
@@ -3406,7 +3452,7 @@ ${res.locals.errorMessage}`;
|
|
3406
3452
|
if (!errorSent) {
|
3407
3453
|
let data2 = "";
|
3408
3454
|
for (const [key, value] of Object.entries(chunk)) {
|
3409
|
-
data2 += `${key}: ${typeof value === "string" ? value :
|
3455
|
+
data2 += `${key}: ${typeof value === "string" ? value : safeStringify3(value)}
|
3410
3456
|
`;
|
3411
3457
|
}
|
3412
3458
|
data2 += "\n";
|
@@ -3810,7 +3856,7 @@ function generateOpenApiSpecs(schemaValidator, serverUrls, serverDescriptions, a
|
|
3810
3856
|
}
|
3811
3857
|
|
3812
3858
|
// src/http/sdk/sdkClient.ts
|
3813
|
-
import { hashString, safeStringify as
|
3859
|
+
import { hashString, safeStringify as safeStringify4, toRecord as toRecord2 } from "@forklaunch/common";
|
3814
3860
|
|
3815
3861
|
// src/http/guards/isSdkRouter.ts
|
3816
3862
|
function isSdkRouter(value) {
|
@@ -3822,12 +3868,12 @@ function mapToSdk(schemaValidator, routerMap, runningPath = void 0) {
|
|
3822
3868
|
const routerUniquenessCache = /* @__PURE__ */ new Set();
|
3823
3869
|
return Object.fromEntries(
|
3824
3870
|
Object.entries(routerMap).map(([key, value]) => {
|
3825
|
-
if (routerUniquenessCache.has(hashString(
|
3871
|
+
if (routerUniquenessCache.has(hashString(safeStringify4(value)))) {
|
3826
3872
|
throw new Error(
|
3827
3873
|
`SdkClient: Cannot use the same router pointer twice. Please clone the duplicate router with .clone() or only use the router once.`
|
3828
3874
|
);
|
3829
3875
|
}
|
3830
|
-
routerUniquenessCache.add(hashString(
|
3876
|
+
routerUniquenessCache.add(hashString(safeStringify4(value)));
|
3831
3877
|
const currentPath = runningPath ? [runningPath, key].join(".") : key;
|
3832
3878
|
if (isSdkRouter(value)) {
|
3833
3879
|
Object.entries(value.sdkPaths).forEach(([routePath, sdkKey]) => {
|
@@ -3879,12 +3925,36 @@ function mapToFetch(schemaValidator, routerMap) {
|
|
3879
3925
|
return (version ? toRecord2(toRecord2(flattenedFetchMap[path])[method ?? "GET"])[version] : toRecord2(flattenedFetchMap[path])[method ?? "GET"])(path, reqInit[0]);
|
3880
3926
|
});
|
3881
3927
|
}
|
3882
|
-
function sdkClient(schemaValidator, routerMap) {
|
3883
|
-
|
3928
|
+
function sdkClient(schemaValidator, routerMap, options2) {
|
3929
|
+
if (options2?.lazyEvaluation) {
|
3930
|
+
let _sdk;
|
3931
|
+
let _fetch;
|
3932
|
+
const lazyClient = {
|
3933
|
+
_finalizedSdk: true,
|
3934
|
+
get sdk() {
|
3935
|
+
if (!_sdk) {
|
3936
|
+
_sdk = mapToSdk(schemaValidator, routerMap);
|
3937
|
+
}
|
3938
|
+
return _sdk;
|
3939
|
+
},
|
3940
|
+
get fetch() {
|
3941
|
+
if (!_fetch) {
|
3942
|
+
_fetch = mapToFetch(schemaValidator, routerMap);
|
3943
|
+
}
|
3944
|
+
return _fetch;
|
3945
|
+
}
|
3946
|
+
};
|
3947
|
+
return lazyClient;
|
3948
|
+
}
|
3949
|
+
const client = {
|
3884
3950
|
_finalizedSdk: true,
|
3885
3951
|
sdk: mapToSdk(schemaValidator, routerMap),
|
3886
3952
|
fetch: mapToFetch(schemaValidator, routerMap)
|
3887
3953
|
};
|
3954
|
+
if (options2?.optimizePerformance) {
|
3955
|
+
return client;
|
3956
|
+
}
|
3957
|
+
return client;
|
3888
3958
|
}
|
3889
3959
|
|
3890
3960
|
// src/http/sdk/sdkRouter.ts
|