@forklaunch/core 0.10.4 → 0.11.0

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,39 @@ 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: auth.basic
78
+ };
79
+ } else if ("jwt" in auth) {
80
+ return {
81
+ type: "jwt",
82
+ auth: auth.jwt
83
+ };
84
+ } else {
85
+ return {
86
+ type: "jwt"
87
+ };
88
+ }
89
+ }
90
+
91
+ // src/http/guards/hasPermissionChecks.ts
92
+ function hasPermissionChecks(maybePermissionedAuth) {
93
+ return typeof maybePermissionedAuth === "object" && maybePermissionedAuth !== null && "mapPermissions" in maybePermissionedAuth && ("allowedPermissions" in maybePermissionedAuth || "forbiddenPermissions" in maybePermissionedAuth);
94
+ }
95
+
96
+ // src/http/guards/hasRoleChecks.ts
97
+ function hasRoleChecks(maybeRoledAuth) {
98
+ return typeof maybeRoledAuth === "object" && maybeRoledAuth !== null && "mapRoles" in maybeRoledAuth && ("allowedRoles" in maybeRoledAuth || "forbiddenRoles" in maybeRoledAuth);
99
+ }
100
+
101
+ // src/http/middleware/request/auth.middleware.ts
70
102
  var invalidAuthorizationTokenFormat = [
71
103
  401,
72
104
  "Invalid Authorization token format."
@@ -97,18 +129,16 @@ async function checkAuthorizationToken(authorizationMethod, authorizationToken,
97
129
  }
98
130
  const [tokenPrefix, token] = authorizationToken.split(" ");
99
131
  let resourceId;
100
- switch (authorizationMethod.method) {
132
+ const { type, auth } = discriminateAuthMethod(authorizationMethod);
133
+ switch (type) {
101
134
  case "jwt": {
102
- if (tokenPrefix !== "Bearer") {
135
+ if (tokenPrefix !== (authorizationMethod.tokenPrefix ?? "Bearer")) {
103
136
  return invalidAuthorizationTokenFormat;
104
137
  }
105
138
  try {
106
139
  const decodedJwt = await jwtVerify(
107
140
  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
- )
141
+ new TextEncoder().encode(process.env.JWT_SECRET)
112
142
  );
113
143
  if (!decodedJwt.payload.sub) {
114
144
  return invalidAuthorizationSubject;
@@ -121,27 +151,24 @@ async function checkAuthorizationToken(authorizationMethod, authorizationToken,
121
151
  break;
122
152
  }
123
153
  case "basic": {
124
- if (authorizationToken !== "Basic") {
154
+ if (authorizationToken !== (authorizationMethod.tokenPrefix ?? "Basic")) {
125
155
  return invalidAuthorizationTokenFormat;
126
156
  }
127
157
  const [username, password] = Buffer.from(token, "base64").toString("utf-8").split(":");
128
158
  if (!username || !password) {
129
159
  return invalidAuthorizationTokenFormat;
130
160
  }
131
- if (!authorizationMethod.login(username, password)) {
161
+ if (!auth.login(username, password)) {
132
162
  return invalidAuthorizationLogin;
133
163
  }
134
164
  resourceId = username;
135
165
  break;
136
166
  }
137
- case "other":
138
- if (tokenPrefix !== authorizationMethod.tokenPrefix) {
139
- return invalidAuthorizationTokenFormat;
140
- }
141
- resourceId = authorizationMethod.decodeResource(token);
142
- break;
167
+ default:
168
+ isNever(type);
169
+ return [401, "Invalid Authorization method."];
143
170
  }
144
- if (authorizationMethod.allowedPermissions || authorizationMethod.forbiddenPermissions) {
171
+ if (hasPermissionChecks(authorizationMethod)) {
145
172
  if (!authorizationMethod.mapPermissions) {
146
173
  return [500, "No permission mapping function provided."];
147
174
  }
@@ -149,49 +176,49 @@ async function checkAuthorizationToken(authorizationMethod, authorizationToken,
149
176
  resourceId,
150
177
  req
151
178
  );
152
- if (authorizationMethod.allowedPermissions) {
179
+ if ("allowedPermissions" in authorizationMethod && authorizationMethod.allowedPermissions) {
153
180
  if (resourcePermissions.intersection(authorizationMethod.allowedPermissions).size === 0) {
154
181
  return invalidAuthorizationTokenPermissions;
155
182
  }
156
183
  }
157
- if (authorizationMethod.forbiddenPermissions) {
184
+ if ("forbiddenPermissions" in authorizationMethod && authorizationMethod.forbiddenPermissions) {
158
185
  if (resourcePermissions.intersection(
159
186
  authorizationMethod.forbiddenPermissions
160
187
  ).size !== 0) {
161
188
  return invalidAuthorizationTokenPermissions;
162
189
  }
163
190
  }
164
- }
165
- if (authorizationMethod.allowedRoles || authorizationMethod.forbiddenRoles) {
191
+ } else if (hasRoleChecks(authorizationMethod)) {
166
192
  if (!authorizationMethod.mapRoles) {
167
193
  return [500, "No role mapping function provided."];
168
194
  }
169
195
  const resourceRoles = await authorizationMethod.mapRoles(resourceId, req);
170
- if (authorizationMethod.allowedRoles) {
196
+ if ("allowedRoles" in authorizationMethod && authorizationMethod.allowedRoles) {
171
197
  if (resourceRoles.intersection(authorizationMethod.allowedRoles).size === 0) {
172
198
  return invalidAuthorizationTokenRoles;
173
199
  }
174
200
  }
175
- if (authorizationMethod.forbiddenRoles) {
201
+ if ("forbiddenRoles" in authorizationMethod && authorizationMethod.forbiddenRoles) {
176
202
  if (resourceRoles.intersection(authorizationMethod.forbiddenRoles).size !== 0) {
177
203
  return invalidAuthorizationTokenRoles;
178
204
  }
179
205
  }
206
+ } else {
207
+ return [401, "Invalid Authorization method."];
180
208
  }
181
- return [401, "Invalid Authorization method."];
182
209
  }
183
210
  async function parseRequestAuth(req, res, next) {
184
211
  const auth = req.contractDetails.auth;
185
212
  if (auth) {
186
213
  const [error, message] = await checkAuthorizationToken(
187
214
  auth,
188
- req.headers[(auth.method === "other" ? auth.headerName : void 0) ?? "Authorization"],
215
+ req.headers[auth.headerName ?? "Authorization"] || req.headers[auth.headerName ?? "authorization"],
189
216
  req
190
217
  ) ?? [];
191
218
  if (error != null) {
192
219
  res.type("text/plain");
193
220
  res.status(error).send(message);
194
- next?.(new Error(message));
221
+ return;
195
222
  }
196
223
  }
197
224
  next?.();
@@ -262,7 +289,7 @@ function isForklaunchRequest(request) {
262
289
  }
263
290
 
264
291
  // src/http/telemetry/pinoLogger.ts
265
- import { isNever, safeStringify } from "@forklaunch/common";
292
+ import { isNever as isNever2, safeStringify } from "@forklaunch/common";
266
293
  import { trace as trace2 } from "@opentelemetry/api";
267
294
  import { logs } from "@opentelemetry/api-logs";
268
295
  import pino from "pino";
@@ -294,7 +321,7 @@ function mapSeverity(level) {
294
321
  case "fatal":
295
322
  return 21;
296
323
  default:
297
- isNever(level);
324
+ isNever2(level);
298
325
  return 0;
299
326
  }
300
327
  }
@@ -1457,6 +1484,11 @@ var trace3 = (_schemaValidator, path, contractDetails, ...handlers) => {
1457
1484
  return typedHandler(_schemaValidator, path, "trace", contractDetails, ...handlers);
1458
1485
  };
1459
1486
 
1487
+ // src/http/handlers/typedAuthHandler.ts
1488
+ function typedAuthHandler(_schemaValidator, _contractDetails, authHandler) {
1489
+ return authHandler;
1490
+ }
1491
+
1460
1492
  // src/http/httpStatusCodes.ts
1461
1493
  var HTTPStatuses = {
1462
1494
  /**
@@ -2441,8 +2473,8 @@ var getCodeForStatus = (status) => {
2441
2473
  var httpStatusCodes_default = HTTPStatuses;
2442
2474
 
2443
2475
  // src/http/mcpGenerator/mcpGenerator.ts
2444
- import { isNever as isNever2, isRecord, safeStringify as safeStringify2 } from "@forklaunch/common";
2445
- import { ZodSchemaValidator } from "@forklaunch/validator/zod";
2476
+ import { isNever as isNever3, isRecord, safeStringify as safeStringify2 } from "@forklaunch/common";
2477
+ import { string, ZodSchemaValidator } from "@forklaunch/validator/zod";
2446
2478
  import { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js";
2447
2479
 
2448
2480
  // src/http/router/unpackRouters.ts
@@ -2499,14 +2531,15 @@ function generateMcpServer(schemaValidator, protocol, host, port, version, route
2499
2531
  ...route.contractDetails.params ? { params: schemaValidator.schemify(route.contractDetails.params) } : {},
2500
2532
  ...route.contractDetails.query ? { query: schemaValidator.schemify(route.contractDetails.query) } : {},
2501
2533
  ...route.contractDetails.requestHeaders ? {
2502
- headers: schemaValidator.schemify(
2503
- route.contractDetails.requestHeaders
2504
- )
2534
+ headers: schemaValidator.schemify({
2535
+ ...route.contractDetails.requestHeaders,
2536
+ ...route.contractDetails.auth ? {
2537
+ [route.contractDetails.auth.headerName ?? "authorization"]: string.startsWith(
2538
+ route.contractDetails.auth.tokenPrefix ?? ("basic" in route.contractDetails.auth ? "Basic " : "Bearer ")
2539
+ )
2540
+ } : {}
2541
+ })
2505
2542
  } : {}
2506
- // TODO: support auth
2507
- // ...(route.contractDetails.auth
2508
- // ? { auth: route.contractDetails.auth }
2509
- // : {})
2510
2543
  };
2511
2544
  mcpServer.tool(
2512
2545
  route.contractDetails.name,
@@ -2568,7 +2601,7 @@ function generateMcpServer(schemaValidator, protocol, host, port, version, route
2568
2601
  break;
2569
2602
  }
2570
2603
  default: {
2571
- isNever2(discriminatedBody.parserType);
2604
+ isNever3(discriminatedBody.parserType);
2572
2605
  parsedBody = safeStringify2(body);
2573
2606
  break;
2574
2607
  }
@@ -2718,7 +2751,7 @@ ${parseErrors.join("\n\n")}`
2718
2751
  // src/http/middleware/response/enrichExpressLikeSend.middleware.ts
2719
2752
  import {
2720
2753
  isAsyncGenerator,
2721
- isNever as isNever3,
2754
+ isNever as isNever4,
2722
2755
  isNodeJsWriteableStream,
2723
2756
  isRecord as isRecord2,
2724
2757
  readableStreamToAsyncIterable,
@@ -2855,7 +2888,7 @@ ${res.locals.errorMessage}`;
2855
2888
  res.bodyData = data;
2856
2889
  break;
2857
2890
  default:
2858
- isNever3(parserType);
2891
+ isNever4(parserType);
2859
2892
  res.bodyData = data;
2860
2893
  break;
2861
2894
  }
@@ -2901,7 +2934,7 @@ function transformBasePath(basePath) {
2901
2934
  }
2902
2935
  return `/${basePath}`;
2903
2936
  }
2904
- function generateOpenApiDocument(protocol, host, port, tags, paths, otherServers) {
2937
+ function generateOpenApiDocument(protocol, host, port, tags, paths, securitySchemes, otherServers) {
2905
2938
  return {
2906
2939
  openapi: "3.1.0",
2907
2940
  info: {
@@ -2909,13 +2942,7 @@ function generateOpenApiDocument(protocol, host, port, tags, paths, otherServers
2909
2942
  version: process.env.VERSION || "1.0.0"
2910
2943
  },
2911
2944
  components: {
2912
- securitySchemes: {
2913
- bearer: {
2914
- type: "http",
2915
- scheme: "bearer",
2916
- bearerFormat: "JWT"
2917
- }
2918
- }
2945
+ securitySchemes
2919
2946
  },
2920
2947
  tags,
2921
2948
  servers: [
@@ -2947,6 +2974,7 @@ function contentResolver(schemaValidator, body, contentType) {
2947
2974
  function generateSwaggerDocument(schemaValidator, protocol, host, port, routers, otherServers) {
2948
2975
  const tags = [];
2949
2976
  const paths = {};
2977
+ const securitySchemes = {};
2950
2978
  unpackRouters(routers).forEach(({ fullPath, router, sdkPath }) => {
2951
2979
  const controllerName = transformBasePath(fullPath);
2952
2980
  tags.push({
@@ -3043,14 +3071,39 @@ function generateSwaggerDocument(schemaValidator, protocol, host, port, routers,
3043
3071
  description: httpStatusCodes_default[403],
3044
3072
  content: contentResolver(schemaValidator, schemaValidator.string)
3045
3073
  };
3046
- if (route.contractDetails.auth.method === "jwt") {
3074
+ if ("basic" in route.contractDetails.auth) {
3047
3075
  operationObject.security = [
3048
3076
  {
3049
- bearer: Array.from(
3050
- route.contractDetails.auth.allowedPermissions?.values() || []
3077
+ basic: Array.from(
3078
+ "allowedPermissions" in route.contractDetails.auth ? route.contractDetails.auth.allowedPermissions?.values() || [] : []
3051
3079
  )
3052
3080
  }
3053
3081
  ];
3082
+ securitySchemes["basic"] = {
3083
+ type: "http",
3084
+ scheme: "basic"
3085
+ };
3086
+ } else if (route.contractDetails.auth) {
3087
+ operationObject.security = [
3088
+ {
3089
+ [route.contractDetails.auth.headerName !== "Authorization" ? "bearer" : "apiKey"]: Array.from(
3090
+ "allowedPermissions" in route.contractDetails.auth ? route.contractDetails.auth.allowedPermissions?.values() || [] : []
3091
+ )
3092
+ }
3093
+ ];
3094
+ if (route.contractDetails.auth.headerName && route.contractDetails.auth.headerName !== "Authorization") {
3095
+ securitySchemes[route.contractDetails.auth.headerName] = {
3096
+ type: "apiKey",
3097
+ in: "header",
3098
+ name: route.contractDetails.auth.headerName
3099
+ };
3100
+ } else {
3101
+ securitySchemes["Authorization"] = {
3102
+ type: "http",
3103
+ scheme: "bearer",
3104
+ bearerFormat: "JWT"
3105
+ };
3106
+ }
3054
3107
  }
3055
3108
  }
3056
3109
  if (route.method !== "middleware") {
@@ -3064,6 +3117,7 @@ function generateSwaggerDocument(schemaValidator, protocol, host, port, routers,
3064
3117
  port,
3065
3118
  tags,
3066
3119
  paths,
3120
+ securitySchemes,
3067
3121
  otherServers
3068
3122
  );
3069
3123
  }
@@ -3129,6 +3183,7 @@ export {
3129
3183
  put,
3130
3184
  recordMetric,
3131
3185
  trace3 as trace,
3186
+ typedAuthHandler,
3132
3187
  typedHandler
3133
3188
  };
3134
3189
  //# sourceMappingURL=index.mjs.map