@forklaunch/core 0.10.4 → 0.11.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.
@@ -66,7 +66,47 @@ function isTypedHandler(maybeTypedHandler) {
66
66
  }
67
67
 
68
68
  // src/http/middleware/request/auth.middleware.ts
69
+ import { isNever } from "@forklaunch/common";
69
70
  import { jwtVerify } from "jose";
71
+
72
+ // src/http/discriminateAuthMethod.ts
73
+ function discriminateAuthMethod(auth) {
74
+ if ("basic" in auth) {
75
+ return {
76
+ type: "basic",
77
+ auth: {
78
+ decodeResource: auth.decodeResource,
79
+ login: auth.basic.login
80
+ }
81
+ };
82
+ } else if ("jwt" in auth) {
83
+ return {
84
+ type: "jwt",
85
+ auth: {
86
+ decodeResource: auth.decodeResource
87
+ }
88
+ };
89
+ } else {
90
+ return {
91
+ type: "jwt",
92
+ auth: {
93
+ decodeResource: auth.decodeResource
94
+ }
95
+ };
96
+ }
97
+ }
98
+
99
+ // src/http/guards/hasPermissionChecks.ts
100
+ function hasPermissionChecks(maybePermissionedAuth) {
101
+ return typeof maybePermissionedAuth === "object" && maybePermissionedAuth !== null && "mapPermissions" in maybePermissionedAuth && ("allowedPermissions" in maybePermissionedAuth || "forbiddenPermissions" in maybePermissionedAuth);
102
+ }
103
+
104
+ // src/http/guards/hasRoleChecks.ts
105
+ function hasRoleChecks(maybeRoledAuth) {
106
+ return typeof maybeRoledAuth === "object" && maybeRoledAuth !== null && "mapRoles" in maybeRoledAuth && ("allowedRoles" in maybeRoledAuth || "forbiddenRoles" in maybeRoledAuth);
107
+ }
108
+
109
+ // src/http/middleware/request/auth.middleware.ts
70
110
  var invalidAuthorizationTokenFormat = [
71
111
  401,
72
112
  "Invalid Authorization token format."
@@ -97,51 +137,52 @@ async function checkAuthorizationToken(authorizationMethod, authorizationToken,
97
137
  }
98
138
  const [tokenPrefix, token] = authorizationToken.split(" ");
99
139
  let resourceId;
100
- switch (authorizationMethod.method) {
140
+ const { type, auth } = discriminateAuthMethod(authorizationMethod);
141
+ switch (type) {
101
142
  case "jwt": {
102
- if (tokenPrefix !== "Bearer") {
143
+ if (tokenPrefix !== (authorizationMethod.tokenPrefix ?? "Bearer")) {
103
144
  return invalidAuthorizationTokenFormat;
104
145
  }
105
146
  try {
106
- const decodedJwt = await jwtVerify(
147
+ const decodedJwt = await auth?.decodeResource?.(token) ?? (await jwtVerify(
107
148
  token,
108
- new TextEncoder().encode(
109
- // TODO: Check this at application startup if there is any route with jwt checking
110
- process.env.JWT_SECRET
111
- )
112
- );
113
- if (!decodedJwt.payload.sub) {
149
+ new TextEncoder().encode(process.env.JWT_SECRET)
150
+ )).payload;
151
+ if (!decodedJwt) {
114
152
  return invalidAuthorizationSubject;
115
153
  }
116
- resourceId = decodedJwt.payload.sub;
154
+ resourceId = decodedJwt;
117
155
  } catch (error) {
118
- req.openTelemetryCollector.error(error);
156
+ req?.openTelemetryCollector.error(error);
119
157
  return invalidAuthorizationToken;
120
158
  }
121
159
  break;
122
160
  }
123
161
  case "basic": {
124
- if (authorizationToken !== "Basic") {
125
- return invalidAuthorizationTokenFormat;
126
- }
127
- const [username, password] = Buffer.from(token, "base64").toString("utf-8").split(":");
128
- if (!username || !password) {
162
+ if (tokenPrefix !== (authorizationMethod.tokenPrefix ?? "Basic")) {
129
163
  return invalidAuthorizationTokenFormat;
130
164
  }
131
- if (!authorizationMethod.login(username, password)) {
132
- return invalidAuthorizationLogin;
165
+ if (auth.decodeResource) {
166
+ resourceId = await auth.decodeResource(token);
167
+ } else {
168
+ const [username, password] = Buffer.from(token, "base64").toString("utf-8").split(":");
169
+ if (!username || !password) {
170
+ return invalidAuthorizationTokenFormat;
171
+ }
172
+ if (!auth.login(username, password)) {
173
+ return invalidAuthorizationLogin;
174
+ }
175
+ resourceId = {
176
+ sub: username
177
+ };
133
178
  }
134
- resourceId = username;
135
179
  break;
136
180
  }
137
- case "other":
138
- if (tokenPrefix !== authorizationMethod.tokenPrefix) {
139
- return invalidAuthorizationTokenFormat;
140
- }
141
- resourceId = authorizationMethod.decodeResource(token);
142
- break;
181
+ default:
182
+ isNever(type);
183
+ return [401, "Invalid Authorization method."];
143
184
  }
144
- if (authorizationMethod.allowedPermissions || authorizationMethod.forbiddenPermissions) {
185
+ if (hasPermissionChecks(authorizationMethod)) {
145
186
  if (!authorizationMethod.mapPermissions) {
146
187
  return [500, "No permission mapping function provided."];
147
188
  }
@@ -149,49 +190,49 @@ async function checkAuthorizationToken(authorizationMethod, authorizationToken,
149
190
  resourceId,
150
191
  req
151
192
  );
152
- if (authorizationMethod.allowedPermissions) {
193
+ if ("allowedPermissions" in authorizationMethod && authorizationMethod.allowedPermissions) {
153
194
  if (resourcePermissions.intersection(authorizationMethod.allowedPermissions).size === 0) {
154
195
  return invalidAuthorizationTokenPermissions;
155
196
  }
156
197
  }
157
- if (authorizationMethod.forbiddenPermissions) {
198
+ if ("forbiddenPermissions" in authorizationMethod && authorizationMethod.forbiddenPermissions) {
158
199
  if (resourcePermissions.intersection(
159
200
  authorizationMethod.forbiddenPermissions
160
201
  ).size !== 0) {
161
202
  return invalidAuthorizationTokenPermissions;
162
203
  }
163
204
  }
164
- }
165
- if (authorizationMethod.allowedRoles || authorizationMethod.forbiddenRoles) {
205
+ } else if (hasRoleChecks(authorizationMethod)) {
166
206
  if (!authorizationMethod.mapRoles) {
167
207
  return [500, "No role mapping function provided."];
168
208
  }
169
209
  const resourceRoles = await authorizationMethod.mapRoles(resourceId, req);
170
- if (authorizationMethod.allowedRoles) {
210
+ if ("allowedRoles" in authorizationMethod && authorizationMethod.allowedRoles) {
171
211
  if (resourceRoles.intersection(authorizationMethod.allowedRoles).size === 0) {
172
212
  return invalidAuthorizationTokenRoles;
173
213
  }
174
214
  }
175
- if (authorizationMethod.forbiddenRoles) {
215
+ if ("forbiddenRoles" in authorizationMethod && authorizationMethod.forbiddenRoles) {
176
216
  if (resourceRoles.intersection(authorizationMethod.forbiddenRoles).size !== 0) {
177
217
  return invalidAuthorizationTokenRoles;
178
218
  }
179
219
  }
220
+ } else {
221
+ return [401, "Invalid Authorization method."];
180
222
  }
181
- return [401, "Invalid Authorization method."];
182
223
  }
183
224
  async function parseRequestAuth(req, res, next) {
184
225
  const auth = req.contractDetails.auth;
185
226
  if (auth) {
186
227
  const [error, message] = await checkAuthorizationToken(
187
228
  auth,
188
- req.headers[(auth.method === "other" ? auth.headerName : void 0) ?? "Authorization"],
229
+ req.headers[auth.headerName ?? "Authorization"] || req.headers[auth.headerName ?? "authorization"],
189
230
  req
190
231
  ) ?? [];
191
232
  if (error != null) {
192
233
  res.type("text/plain");
193
234
  res.status(error).send(message);
194
- next?.(new Error(message));
235
+ return;
195
236
  }
196
237
  }
197
238
  next?.();
@@ -262,7 +303,7 @@ function isForklaunchRequest(request) {
262
303
  }
263
304
 
264
305
  // src/http/telemetry/pinoLogger.ts
265
- import { isNever, safeStringify } from "@forklaunch/common";
306
+ import { isNever as isNever2, safeStringify } from "@forklaunch/common";
266
307
  import { trace as trace2 } from "@opentelemetry/api";
267
308
  import { logs } from "@opentelemetry/api-logs";
268
309
  import pino from "pino";
@@ -294,7 +335,7 @@ function mapSeverity(level) {
294
335
  case "fatal":
295
336
  return 21;
296
337
  default:
297
- isNever(level);
338
+ isNever2(level);
298
339
  return 0;
299
340
  }
300
341
  }
@@ -540,7 +581,18 @@ function parse(req, res, next) {
540
581
  enumerable: true,
541
582
  configurable: false
542
583
  });
543
- req.headers = parsedRequest.value.headers ?? {};
584
+ const parsedHeaders = parsedRequest.value.headers ?? {};
585
+ req.headers = Object.keys(req.headers).reduce(
586
+ (acc, key) => {
587
+ if (parsedHeaders?.[key]) {
588
+ acc[key] = parsedHeaders[key];
589
+ } else {
590
+ acc[key] = req.headers[key];
591
+ }
592
+ return acc;
593
+ },
594
+ {}
595
+ );
544
596
  }
545
597
  if (!parsedRequest.ok) {
546
598
  switch (req.contractDetails.options?.requestValidation) {
@@ -1457,6 +1509,11 @@ var trace3 = (_schemaValidator, path, contractDetails, ...handlers) => {
1457
1509
  return typedHandler(_schemaValidator, path, "trace", contractDetails, ...handlers);
1458
1510
  };
1459
1511
 
1512
+ // src/http/handlers/typedAuthHandler.ts
1513
+ function typedAuthHandler(_schemaValidator, _contractDetails, authHandler) {
1514
+ return authHandler;
1515
+ }
1516
+
1460
1517
  // src/http/httpStatusCodes.ts
1461
1518
  var HTTPStatuses = {
1462
1519
  /**
@@ -2441,8 +2498,8 @@ var getCodeForStatus = (status) => {
2441
2498
  var httpStatusCodes_default = HTTPStatuses;
2442
2499
 
2443
2500
  // src/http/mcpGenerator/mcpGenerator.ts
2444
- import { isNever as isNever2, isRecord, safeStringify as safeStringify2 } from "@forklaunch/common";
2445
- import { ZodSchemaValidator } from "@forklaunch/validator/zod";
2501
+ import { isNever as isNever3, isRecord, safeStringify as safeStringify2 } from "@forklaunch/common";
2502
+ import { string, ZodSchemaValidator } from "@forklaunch/validator/zod";
2446
2503
  import { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js";
2447
2504
 
2448
2505
  // src/http/router/unpackRouters.ts
@@ -2499,14 +2556,15 @@ function generateMcpServer(schemaValidator, protocol, host, port, version, route
2499
2556
  ...route.contractDetails.params ? { params: schemaValidator.schemify(route.contractDetails.params) } : {},
2500
2557
  ...route.contractDetails.query ? { query: schemaValidator.schemify(route.contractDetails.query) } : {},
2501
2558
  ...route.contractDetails.requestHeaders ? {
2502
- headers: schemaValidator.schemify(
2503
- route.contractDetails.requestHeaders
2504
- )
2559
+ headers: schemaValidator.schemify({
2560
+ ...route.contractDetails.requestHeaders,
2561
+ ...route.contractDetails.auth ? {
2562
+ [route.contractDetails.auth.headerName ?? "authorization"]: string.startsWith(
2563
+ route.contractDetails.auth.tokenPrefix ?? ("basic" in route.contractDetails.auth ? "Basic " : "Bearer ")
2564
+ )
2565
+ } : {}
2566
+ })
2505
2567
  } : {}
2506
- // TODO: support auth
2507
- // ...(route.contractDetails.auth
2508
- // ? { auth: route.contractDetails.auth }
2509
- // : {})
2510
2568
  };
2511
2569
  mcpServer.tool(
2512
2570
  route.contractDetails.name,
@@ -2568,7 +2626,7 @@ function generateMcpServer(schemaValidator, protocol, host, port, version, route
2568
2626
  break;
2569
2627
  }
2570
2628
  default: {
2571
- isNever2(discriminatedBody.parserType);
2629
+ isNever3(discriminatedBody.parserType);
2572
2630
  parsedBody = safeStringify2(body);
2573
2631
  break;
2574
2632
  }
@@ -2718,7 +2776,7 @@ ${parseErrors.join("\n\n")}`
2718
2776
  // src/http/middleware/response/enrichExpressLikeSend.middleware.ts
2719
2777
  import {
2720
2778
  isAsyncGenerator,
2721
- isNever as isNever3,
2779
+ isNever as isNever4,
2722
2780
  isNodeJsWriteableStream,
2723
2781
  isRecord as isRecord2,
2724
2782
  readableStreamToAsyncIterable,
@@ -2855,7 +2913,7 @@ ${res.locals.errorMessage}`;
2855
2913
  res.bodyData = data;
2856
2914
  break;
2857
2915
  default:
2858
- isNever3(parserType);
2916
+ isNever4(parserType);
2859
2917
  res.bodyData = data;
2860
2918
  break;
2861
2919
  }
@@ -2901,7 +2959,7 @@ function transformBasePath(basePath) {
2901
2959
  }
2902
2960
  return `/${basePath}`;
2903
2961
  }
2904
- function generateOpenApiDocument(protocol, host, port, tags, paths, otherServers) {
2962
+ function generateOpenApiDocument(protocol, host, port, tags, paths, securitySchemes, otherServers) {
2905
2963
  return {
2906
2964
  openapi: "3.1.0",
2907
2965
  info: {
@@ -2909,13 +2967,7 @@ function generateOpenApiDocument(protocol, host, port, tags, paths, otherServers
2909
2967
  version: process.env.VERSION || "1.0.0"
2910
2968
  },
2911
2969
  components: {
2912
- securitySchemes: {
2913
- bearer: {
2914
- type: "http",
2915
- scheme: "bearer",
2916
- bearerFormat: "JWT"
2917
- }
2918
- }
2970
+ securitySchemes
2919
2971
  },
2920
2972
  tags,
2921
2973
  servers: [
@@ -2947,6 +2999,7 @@ function contentResolver(schemaValidator, body, contentType) {
2947
2999
  function generateSwaggerDocument(schemaValidator, protocol, host, port, routers, otherServers) {
2948
3000
  const tags = [];
2949
3001
  const paths = {};
3002
+ const securitySchemes = {};
2950
3003
  unpackRouters(routers).forEach(({ fullPath, router, sdkPath }) => {
2951
3004
  const controllerName = transformBasePath(fullPath);
2952
3005
  tags.push({
@@ -3043,14 +3096,39 @@ function generateSwaggerDocument(schemaValidator, protocol, host, port, routers,
3043
3096
  description: httpStatusCodes_default[403],
3044
3097
  content: contentResolver(schemaValidator, schemaValidator.string)
3045
3098
  };
3046
- if (route.contractDetails.auth.method === "jwt") {
3099
+ if ("basic" in route.contractDetails.auth) {
3100
+ operationObject.security = [
3101
+ {
3102
+ basic: Array.from(
3103
+ "allowedPermissions" in route.contractDetails.auth ? route.contractDetails.auth.allowedPermissions?.values() || [] : []
3104
+ )
3105
+ }
3106
+ ];
3107
+ securitySchemes["basic"] = {
3108
+ type: "http",
3109
+ scheme: "basic"
3110
+ };
3111
+ } else if (route.contractDetails.auth) {
3047
3112
  operationObject.security = [
3048
3113
  {
3049
- bearer: Array.from(
3050
- route.contractDetails.auth.allowedPermissions?.values() || []
3114
+ [route.contractDetails.auth.headerName !== "Authorization" ? "bearer" : "apiKey"]: Array.from(
3115
+ "allowedPermissions" in route.contractDetails.auth ? route.contractDetails.auth.allowedPermissions?.values() || [] : []
3051
3116
  )
3052
3117
  }
3053
3118
  ];
3119
+ if (route.contractDetails.auth.headerName && route.contractDetails.auth.headerName !== "Authorization") {
3120
+ securitySchemes[route.contractDetails.auth.headerName] = {
3121
+ type: "apiKey",
3122
+ in: "header",
3123
+ name: route.contractDetails.auth.headerName
3124
+ };
3125
+ } else {
3126
+ securitySchemes["Authorization"] = {
3127
+ type: "http",
3128
+ scheme: "bearer",
3129
+ bearerFormat: "JWT"
3130
+ };
3131
+ }
3054
3132
  }
3055
3133
  }
3056
3134
  if (route.method !== "middleware") {
@@ -3064,6 +3142,7 @@ function generateSwaggerDocument(schemaValidator, protocol, host, port, routers,
3064
3142
  port,
3065
3143
  tags,
3066
3144
  paths,
3145
+ securitySchemes,
3067
3146
  otherServers
3068
3147
  );
3069
3148
  }
@@ -3129,6 +3208,7 @@ export {
3129
3208
  put,
3130
3209
  recordMetric,
3131
3210
  trace3 as trace,
3211
+ typedAuthHandler,
3132
3212
  typedHandler
3133
3213
  };
3134
3214
  //# sourceMappingURL=index.mjs.map