@forklaunch/core 0.14.9 → 0.14.11

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.
@@ -81,6 +81,9 @@ 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";
@@ -99,10 +102,12 @@ function createHmacToken({
99
102
  const hmac = createHmac("sha256", secretKey);
100
103
  const bodyString = body ? `${safeStringify(body)}
101
104
  ` : void 0;
102
- hmac.update(`${method}
105
+ hmac.update(
106
+ `${method}
103
107
  ${path}
104
- ${bodyString}${timestamp}
105
- ${nonce}`);
108
+ ${bodyString}${timestamp.toISOString()}
109
+ ${nonce}`
110
+ );
106
111
  return hmac.digest("base64");
107
112
  }
108
113
 
@@ -122,6 +127,12 @@ function isJwtAuthMethod(maybeJwtAuthMethod) {
122
127
  }
123
128
 
124
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
+ };
125
136
  async function discriminateAuthMethod(auth) {
126
137
  let authMethod;
127
138
  if (isBasicAuthMethod(auth)) {
@@ -146,8 +157,17 @@ async function discriminateAuthMethod(auth) {
146
157
  } else {
147
158
  let jwks;
148
159
  if ("jwksPublicKeyUrl" in jwt) {
149
- const jwksResponse = await fetch(jwt.jwksPublicKeyUrl);
150
- jwks = (await jwksResponse.json()).keys;
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
+ }
151
171
  } else if ("jwksPublicKey" in jwt) {
152
172
  jwks = [jwt.jwksPublicKey];
153
173
  }
@@ -157,6 +177,9 @@ async function discriminateAuthMethod(auth) {
157
177
  const { payload } = await jwtVerify(token, key);
158
178
  return payload;
159
179
  } catch {
180
+ memoizedJwks.value = null;
181
+ memoizedJwks.lastUpdated = null;
182
+ memoizedJwks.ttl = DEFAULT_TTL;
160
183
  continue;
161
184
  }
162
185
  }
@@ -260,7 +283,7 @@ function parseHmacTokenPart(part, expectedKey) {
260
283
  if (key !== expectedKey || rest.length === 0) return void 0;
261
284
  return rest.join("=");
262
285
  }
263
- async function checkAuthorizationToken(authorizationMethod, globalOptions, authorizationToken, req) {
286
+ async function checkAuthorizationToken(req, authorizationMethod, authorizationToken, globalOptions) {
264
287
  if (authorizationMethod == null) {
265
288
  return void 0;
266
289
  }
@@ -275,7 +298,7 @@ async function checkAuthorizationToken(authorizationMethod, globalOptions, autho
275
298
  if (!tokenParts.length || !tokenPrefix) {
276
299
  return invalidAuthorizationTokenFormat;
277
300
  }
278
- let resourceId;
301
+ let sessionPayload;
279
302
  const { type, auth } = await discriminateAuthMethod(
280
303
  collapsedAuthorizationMethod
281
304
  );
@@ -299,7 +322,7 @@ async function checkAuthorizationToken(authorizationMethod, globalOptions, autho
299
322
  method: req?.method ?? "",
300
323
  path: req?.path ?? "",
301
324
  body: req?.body,
302
- timestamp: parsedTimestamp,
325
+ timestamp: new Date(parsedTimestamp),
303
326
  nonce: parsedNonce,
304
327
  signature: parsedSignature,
305
328
  secretKey: collapsedAuthorizationMethod.hmac.secretKeys[parsedKeyId]
@@ -307,7 +330,7 @@ async function checkAuthorizationToken(authorizationMethod, globalOptions, autho
307
330
  if (!verificationResult) {
308
331
  return invalidAuthorizationSignature;
309
332
  }
310
- resourceId = null;
333
+ sessionPayload = null;
311
334
  break;
312
335
  }
313
336
  case "jwt": {
@@ -320,7 +343,7 @@ async function checkAuthorizationToken(authorizationMethod, globalOptions, autho
320
343
  if (!decodedJwt) {
321
344
  return invalidAuthorizationToken;
322
345
  }
323
- resourceId = decodedJwt;
346
+ sessionPayload = decodedJwt;
324
347
  } catch (error) {
325
348
  req?.openTelemetryCollector.error(error);
326
349
  return invalidAuthorizationToken;
@@ -333,7 +356,7 @@ async function checkAuthorizationToken(authorizationMethod, globalOptions, autho
333
356
  return invalidAuthorizationTokenFormat;
334
357
  }
335
358
  if (auth.decodeResource) {
336
- resourceId = await auth.decodeResource(token);
359
+ sessionPayload = await auth.decodeResource(token);
337
360
  } else {
338
361
  const [username, password] = Buffer.from(token, "base64").toString("utf-8").split(":");
339
362
  if (!username || !password) {
@@ -342,7 +365,7 @@ async function checkAuthorizationToken(authorizationMethod, globalOptions, autho
342
365
  if (!auth.login(username, password)) {
343
366
  return invalidAuthorizationLogin;
344
367
  }
345
- resourceId = {
368
+ sessionPayload = {
346
369
  sub: username
347
370
  };
348
371
  }
@@ -352,16 +375,29 @@ async function checkAuthorizationToken(authorizationMethod, globalOptions, autho
352
375
  isNever(type);
353
376
  return [401, "Invalid Authorization method."];
354
377
  }
355
- if (isHmacMethod(collapsedAuthorizationMethod) && resourceId == null) {
378
+ if (isHmacMethod(collapsedAuthorizationMethod) && sessionPayload == null) {
356
379
  return;
357
380
  }
358
- if (resourceId == null) {
381
+ if (sessionPayload == null) {
359
382
  return invalidAuthorizationToken;
360
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
+ }
361
397
  if (hasScopeChecks(collapsedAuthorizationMethod)) {
362
398
  if (collapsedAuthorizationMethod.surfaceScopes) {
363
399
  const resourceScopes = await collapsedAuthorizationMethod.surfaceScopes(
364
- resourceId,
400
+ sessionPayload,
365
401
  req
366
402
  );
367
403
  if (collapsedAuthorizationMethod.scopeHeirarchy) {
@@ -380,7 +416,7 @@ async function checkAuthorizationToken(authorizationMethod, globalOptions, autho
380
416
  return [500, "No permission surfacing function provided."];
381
417
  }
382
418
  const resourcePermissions = await collapsedAuthorizationMethod.surfacePermissions(
383
- resourceId,
419
+ sessionPayload,
384
420
  req
385
421
  );
386
422
  if ("allowedPermissions" in collapsedAuthorizationMethod && collapsedAuthorizationMethod.allowedPermissions) {
@@ -402,7 +438,7 @@ async function checkAuthorizationToken(authorizationMethod, globalOptions, autho
402
438
  return [500, "No role surfacing function provided."];
403
439
  }
404
440
  const resourceRoles = await collapsedAuthorizationMethod.surfaceRoles(
405
- resourceId,
441
+ sessionPayload,
406
442
  req
407
443
  );
408
444
  if ("allowedRoles" in collapsedAuthorizationMethod && collapsedAuthorizationMethod.allowedRoles) {
@@ -422,10 +458,10 @@ async function checkAuthorizationToken(authorizationMethod, globalOptions, autho
422
458
  async function parseRequestAuth(req, res, next) {
423
459
  const auth = req.contractDetails.auth;
424
460
  const [error, message] = await checkAuthorizationToken(
461
+ req,
425
462
  auth,
426
- req._globalOptions?.()?.auth,
427
463
  req.headers[auth?.headerName ?? "Authorization"] || req.headers[auth?.headerName ?? "authorization"],
428
- req
464
+ req._globalOptions?.()?.auth
429
465
  ) ?? [];
430
466
  if (error != null) {
431
467
  res.type("text/plain");
@@ -804,7 +840,7 @@ function enrichDetails(path, contractDetails, requestSchema, responseSchemas, op
804
840
  // src/http/middleware/request/parse.middleware.ts
805
841
  import { isRecord } from "@forklaunch/common";
806
842
  import {
807
- prettyPrintParseErrors
843
+ prettyPrintParseErrors as prettyPrintParseErrors2
808
844
  } from "@forklaunch/validator";
809
845
 
810
846
  // src/http/guards/hasSend.ts
@@ -843,7 +879,7 @@ function parse(req, res, next) {
843
879
  req.version = version;
844
880
  res.version = req.version;
845
881
  } else {
846
- runningParseErrors += prettyPrintParseErrors(
882
+ runningParseErrors += prettyPrintParseErrors2(
847
883
  parsingResult.errors,
848
884
  `Version ${version} request`
849
885
  );
@@ -901,7 +937,7 @@ function parse(req, res, next) {
901
937
  res.status(400);
902
938
  if (hasSend(res)) {
903
939
  res.send(
904
- `${collectedParseErrors ?? prettyPrintParseErrors(parsedRequest.errors, "Request")}
940
+ `${collectedParseErrors ?? prettyPrintParseErrors2(parsedRequest.errors, "Request")}
905
941
 
906
942
  Correlation id: ${req.context.correlationId ?? "No correlation ID"}`
907
943
  );
@@ -911,7 +947,7 @@ Correlation id: ${req.context.correlationId ?? "No correlation ID"}`
911
947
  return;
912
948
  case "warning":
913
949
  req.openTelemetryCollector.warn(
914
- collectedParseErrors ?? prettyPrintParseErrors(parsedRequest.errors, "Request")
950
+ collectedParseErrors ?? prettyPrintParseErrors2(parsedRequest.errors, "Request")
915
951
  );
916
952
  break;
917
953
  case "none":
@@ -3196,7 +3232,7 @@ function generateMcpServer(schemaValidator, protocol, host, port, version, appli
3196
3232
 
3197
3233
  // src/http/middleware/response/parse.middleware.ts
3198
3234
  import {
3199
- prettyPrintParseErrors as prettyPrintParseErrors2
3235
+ prettyPrintParseErrors as prettyPrintParseErrors3
3200
3236
  } from "@forklaunch/validator";
3201
3237
 
3202
3238
  // src/http/guards/isResponseCompiledSchema.ts
@@ -3258,13 +3294,13 @@ function parse2(req, res, next) {
3258
3294
  );
3259
3295
  const parseErrors = [];
3260
3296
  if (!parsedHeaders.ok) {
3261
- const headerErrors = prettyPrintParseErrors2(parsedHeaders.errors, "Header");
3297
+ const headerErrors = prettyPrintParseErrors3(parsedHeaders.errors, "Header");
3262
3298
  if (headerErrors) {
3263
3299
  parseErrors.push(headerErrors);
3264
3300
  }
3265
3301
  }
3266
3302
  if (!parsedResponse.ok) {
3267
- const responseErrors = prettyPrintParseErrors2(
3303
+ const responseErrors = prettyPrintParseErrors3(
3268
3304
  parsedResponse.errors,
3269
3305
  "Response"
3270
3306
  );