@pikku/core 0.6.12 → 0.6.14

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.
Files changed (79) hide show
  1. package/CHANGELOG.md +13 -0
  2. package/dist/channel/channel-handler.d.ts +2 -2
  3. package/dist/channel/channel-handler.js +4 -4
  4. package/dist/channel/channel-runner.d.ts +5 -4
  5. package/dist/channel/channel-runner.js +8 -14
  6. package/dist/channel/channel.types.d.ts +11 -18
  7. package/dist/channel/local/local-channel-handler.d.ts +1 -3
  8. package/dist/channel/local/local-channel-handler.js +0 -3
  9. package/dist/channel/local/local-channel-runner.js +49 -33
  10. package/dist/channel/pikku-abstract-channel-handler.d.ts +4 -7
  11. package/dist/channel/pikku-abstract-channel-handler.js +1 -5
  12. package/dist/channel/serverless/serverless-channel-runner.js +45 -38
  13. package/dist/handle-error.d.ts +14 -0
  14. package/dist/handle-error.js +53 -0
  15. package/dist/http/http-route-runner.d.ts +34 -11
  16. package/dist/http/http-route-runner.js +177 -106
  17. package/dist/http/http-routes.types.d.ts +15 -9
  18. package/dist/http/index.d.ts +0 -1
  19. package/dist/http/index.js +0 -1
  20. package/dist/http/pikku-http-abstract-request.d.ts +4 -1
  21. package/dist/http/pikku-http-abstract-request.js +7 -0
  22. package/dist/index.d.ts +2 -0
  23. package/dist/index.js +2 -0
  24. package/dist/middleware/auth-api-key.d.ts +9 -0
  25. package/dist/middleware/auth-api-key.js +23 -0
  26. package/dist/middleware/auth-cookie-middleware.d.ts +11 -0
  27. package/dist/middleware/auth-cookie-middleware.js +36 -0
  28. package/dist/middleware/auth-jwt-middleware.d.ts +10 -0
  29. package/dist/middleware/auth-jwt-middleware.js +32 -0
  30. package/dist/middleware/auth-query-middleware.d.ts +10 -0
  31. package/dist/middleware/auth-query-middleware.js +21 -0
  32. package/dist/middleware/index.d.ts +4 -0
  33. package/dist/middleware/index.js +4 -0
  34. package/dist/middleware-runner.d.ts +22 -0
  35. package/dist/middleware-runner.js +28 -0
  36. package/dist/scheduler/scheduler.types.d.ts +2 -0
  37. package/dist/schema.d.ts +1 -1
  38. package/dist/schema.js +3 -6
  39. package/dist/services/user-session-service.d.ts +20 -3
  40. package/dist/services/user-session-service.js +35 -1
  41. package/dist/types/core.types.d.ts +11 -15
  42. package/dist/types/functions.types.d.ts +4 -4
  43. package/lcov.info +1572 -1729
  44. package/package.json +1 -2
  45. package/src/channel/channel-handler.ts +6 -11
  46. package/src/channel/channel-runner.ts +13 -32
  47. package/src/channel/channel.types.ts +13 -39
  48. package/src/channel/local/local-channel-handler.ts +1 -7
  49. package/src/channel/local/local-channel-runner.test.ts +2 -5
  50. package/src/channel/local/local-channel-runner.ts +72 -54
  51. package/src/channel/pikku-abstract-channel-handler.test.ts +3 -28
  52. package/src/channel/pikku-abstract-channel-handler.ts +3 -9
  53. package/src/channel/serverless/serverless-channel-runner.ts +68 -56
  54. package/src/handle-error.ts +67 -0
  55. package/src/http/http-route-runner.test.ts +12 -45
  56. package/src/http/http-route-runner.ts +256 -182
  57. package/src/http/http-routes.types.ts +15 -10
  58. package/src/http/index.ts +0 -2
  59. package/src/http/pikku-http-abstract-request.ts +8 -1
  60. package/src/index.ts +2 -0
  61. package/src/middleware/auth-api-key.ts +32 -0
  62. package/src/middleware/auth-cookie-middleware.ts +54 -0
  63. package/src/middleware/auth-jwt-middleware.ts +47 -0
  64. package/src/middleware/auth-query-middleware.ts +33 -0
  65. package/src/middleware/index.ts +4 -0
  66. package/src/middleware-runner.ts +43 -0
  67. package/src/scheduler/scheduler.types.ts +2 -0
  68. package/src/schema.ts +4 -8
  69. package/src/services/user-session-service.ts +45 -3
  70. package/src/types/core.types.ts +19 -18
  71. package/src/types/functions.types.ts +11 -7
  72. package/tsconfig.tsbuildinfo +1 -1
  73. package/dist/http/http-session-service.d.ts +0 -15
  74. package/dist/http/http-session-service.js +0 -1
  75. package/dist/http/pikku-http-session-service.d.ts +0 -45
  76. package/dist/http/pikku-http-session-service.js +0 -92
  77. package/src/http/http-session-service.ts +0 -19
  78. package/src/http/pikku-http-session-service.test.ts +0 -106
  79. package/src/http/pikku-http-session-service.ts +0 -135
@@ -1,43 +1,106 @@
1
- import { getErrorResponse } from '../errors/error-handler.js';
2
1
  import { verifyPermissions } from '../permissions.js';
3
2
  import { match } from 'path-to-regexp';
4
3
  import { PikkuHTTPAbstractRequest } from './pikku-http-abstract-request.js';
5
4
  import { PikkuHTTPAbstractResponse } from './pikku-http-abstract-response.js';
6
- import { ForbiddenError, NotFoundError, NotImplementedError, } from '../errors/errors.js';
5
+ import { ForbiddenError, MissingSessionError, NotFoundError, } from '../errors/errors.js';
7
6
  import crypto from 'crypto';
8
7
  import { closeSessionServices } from '../utils.js';
9
- import { getSchema, validateAndCoerce } from '../schema.js';
8
+ import { coerceQueryStringToArray, validateSchema } from '../schema.js';
9
+ import { LocalUserSessionService } from '../services/user-session-service.js';
10
+ import { runMiddleware } from '../middleware-runner.js';
11
+ import { handleError } from '../handle-error.js';
12
+ /**
13
+ * Initialize global state for HTTP routes and middleware if not already available
14
+ */
10
15
  if (!globalThis.pikku?.httpRoutes) {
11
16
  globalThis.pikku = globalThis.pikku || {};
17
+ globalThis.pikku.httpMiddleware = [];
12
18
  globalThis.pikku.httpRoutes = [];
13
19
  globalThis.pikku.httpRoutesMeta = [];
14
20
  }
21
+ /**
22
+ * Get or set the global HTTP routes
23
+ *
24
+ * @param {CoreHTTPFunctionRoute<any, any, any>[]} [data] - Optional routes data to set
25
+ * @returns {CoreHTTPFunctionRoute<any, any, any>[]} Current routes
26
+ */
15
27
  const httpRoutes = (data) => {
16
28
  if (data) {
17
29
  globalThis.pikku.httpRoutes = data;
18
30
  }
19
31
  return globalThis.pikku.httpRoutes;
20
32
  };
33
+ /**
34
+ * Get or set the global HTTP route metadata
35
+ *
36
+ * @param {HTTPRoutesMeta} [data] - Optional route metadata to set
37
+ * @returns {HTTPRoutesMeta} Current route metadata
38
+ */
21
39
  const httpRoutesMeta = (data) => {
22
40
  if (data) {
23
41
  globalThis.pikku.httpRoutesMeta = data;
24
42
  }
25
43
  return globalThis.pikku.httpRoutesMeta;
26
44
  };
45
+ /**
46
+ * Get or set the global HTTP middleware
47
+ *
48
+ * @param {HTTPRouteMiddleware[]} [data] - Optional middleware to set
49
+ * @returns {HTTPRouteMiddleware[]} Current middleware
50
+ */
51
+ const httpMiddleware = (data) => {
52
+ if (data) {
53
+ globalThis.pikku.httpMiddleware = data;
54
+ }
55
+ return globalThis.pikku.httpMiddleware;
56
+ };
57
+ /**
58
+ * Add middleware to a specific route or globally
59
+ *
60
+ * @param {APIMiddleware[] | string} routeOrMiddleware - Route pattern to match or middleware array
61
+ * @param {APIMiddleware} [middleware] - Middleware to add (required if first param is a string)
62
+ */
63
+ export const addMiddleware = (routeOrMiddleware, middleware) => {
64
+ if (typeof routeOrMiddleware === 'string') {
65
+ globalThis.pikku.httpMiddleware.push({
66
+ route: routeOrMiddleware,
67
+ middleware: middleware,
68
+ });
69
+ }
70
+ else {
71
+ globalThis.pikku.httpMiddleware.push({
72
+ route: '*',
73
+ middleware: routeOrMiddleware,
74
+ });
75
+ }
76
+ };
77
+ /**
78
+ * Add a route to the global routes registry
79
+ *
80
+ * @param {CoreHTTPFunctionRoute} route - Route configuration to add
81
+ */
27
82
  export const addRoute = (route) => {
28
83
  httpRoutes().push(route);
29
84
  };
85
+ /**
86
+ * Remove all routes from the global registry
87
+ */
30
88
  export const clearRoutes = () => {
31
89
  httpRoutes([]);
32
90
  };
33
91
  /**
92
+ * Set the HTTP routes metadata
93
+ *
94
+ * @param {HTTPRoutesMeta} routeMeta - Metadata for routes
34
95
  * @ignore
35
96
  */
36
- export const setHTTPRoutesMeta = (_routeMeta) => {
37
- httpRoutesMeta(_routeMeta);
97
+ export const setHTTPRoutesMeta = (routeMeta) => {
98
+ httpRoutesMeta(routeMeta);
38
99
  };
39
100
  /**
40
- * Returns all the registered routes and associated metadata.
101
+ * Returns all the registered routes and associated metadata
102
+ *
103
+ * @returns {Object} Object containing routes and routesMeta
41
104
  * @internal
42
105
  */
43
106
  export const getRoutes = () => {
@@ -46,69 +109,51 @@ export const getRoutes = () => {
46
109
  routesMeta: httpRoutesMeta(),
47
110
  };
48
111
  };
49
- const getMatchingRoute = (schemaService, requestType, requestPath) => {
112
+ /**
113
+ * Find a route that matches the given request type and path
114
+ *
115
+ * @param {string} requestType - HTTP method (GET, POST, etc.)
116
+ * @param {string} requestPath - URL path to match
117
+ * @returns {Object | undefined} Matching route information or undefined if no match
118
+ */
119
+ const getMatchingRoute = (requestType, requestPath) => {
50
120
  for (const route of httpRoutes()) {
51
- // TODO: This is a performance improvement, but we could
52
- // run against all routes if we want to return a 405 method.
53
- // Probably want a cache to support.
121
+ // Skip routes that don't match the request method
54
122
  if (route.method !== requestType.toLowerCase()) {
55
123
  continue;
56
124
  }
125
+ // Create path matcher function
57
126
  const matchFunc = match(`/${route.route}`.replace(/^\/\//, '/'), {
58
127
  decode: decodeURIComponent,
59
128
  });
129
+ // Try to match the path
60
130
  const matchedPath = matchFunc(requestPath.replace(/^\/\//, '/'));
61
131
  if (matchedPath) {
62
- // TODO: Cache this loop as a performance improvement
132
+ // Get all middleware for this route
133
+ const globalMiddleware = httpMiddleware()
134
+ .filter((m) => m.route === '*' || new RegExp(m.route).test(route.route))
135
+ .map((m) => m.middleware)
136
+ .flat();
137
+ // Find schema for this route
63
138
  const schemaName = httpRoutesMeta().find((routeMeta) => routeMeta.method === route.method && routeMeta.route === route.route)?.input;
64
- if (schemaName && schemaService) {
65
- const schema = getSchema(schemaName);
66
- schemaService.compileSchema(schemaName, schema);
67
- }
68
- return { matchedPath, params: matchedPath.params, route, schemaName };
69
- }
70
- }
71
- return undefined;
72
- };
73
- export const getUserSession = async (httpSessionService, auth, request) => {
74
- if (httpSessionService) {
75
- return (await httpSessionService.getUserSession(auth, request));
76
- }
77
- else if (auth) {
78
- throw new NotImplementedError('Session service not implemented');
79
- }
80
- return undefined;
81
- };
82
- export const loadUserSession = async (skipUserSession, requiresSession, http, matchedPath, route, logger, httpSessionService) => {
83
- if (skipUserSession && requiresSession) {
84
- throw new Error("Can't skip trying to get user session if auth is required");
85
- }
86
- if (skipUserSession === false) {
87
- try {
88
- if (http?.request) {
89
- return await getUserSession(httpSessionService, requiresSession, http.request);
90
- }
91
- else if (requiresSession) {
92
- logger.error({
93
- action: 'Can only get user session with HTTP request',
94
- path: matchedPath,
95
- route,
96
- });
97
- throw new Error('Can only get user session with HTTP request');
98
- }
99
- }
100
- catch (e) {
101
- if (requiresSession) {
102
- logger.info({
103
- action: 'Rejecting route (invalid session)',
104
- path: matchedPath,
105
- });
106
- throw e;
107
- }
139
+ return {
140
+ matchedPath,
141
+ params: matchedPath.params,
142
+ route,
143
+ middleware: [...globalMiddleware, ...(route.middleware || [])],
144
+ schemaName,
145
+ };
108
146
  }
109
147
  }
110
148
  return undefined;
111
149
  };
150
+ /**
151
+ * Create an HTTP interaction object from request and response
152
+ *
153
+ * @param {PikkuRequest | undefined} request - The HTTP request object
154
+ * @param {PikkuResponse | undefined} response - The HTTP response object
155
+ * @returns {PikkuHTTP | undefined} HTTP interaction object or undefined
156
+ */
112
157
  export const createHTTPInteraction = (request, response) => {
113
158
  let http = undefined;
114
159
  if (request instanceof PikkuHTTPAbstractRequest ||
@@ -123,70 +168,55 @@ export const createHTTPInteraction = (request, response) => {
123
168
  }
124
169
  return http;
125
170
  };
126
- export const handleError = (e, http, trackerId, logger, logWarningsForStatusCodes, respondWith404, bubbleError) => {
127
- if (e instanceof NotFoundError && !respondWith404) {
128
- return;
129
- }
130
- const errorResponse = getErrorResponse(e);
131
- if (errorResponse != null) {
132
- http?.response?.setStatus(errorResponse.status);
133
- http?.response?.setJson({
134
- message: errorResponse.message,
135
- payload: e.payload,
136
- traceId: trackerId,
137
- });
138
- if (logWarningsForStatusCodes.includes(errorResponse.status)) {
139
- logger.warn(`Warning id: ${trackerId}`);
140
- logger.warn(e);
141
- }
142
- }
143
- else {
144
- logger.warn(`Error id: ${trackerId}`);
145
- logger.error(e);
146
- http?.response?.setStatus(500);
147
- http?.response?.setJson({ errorId: trackerId });
148
- }
149
- if (e instanceof NotFoundError) {
150
- http?.response?.end();
151
- }
152
- if (bubbleError) {
153
- throw e;
154
- }
155
- };
156
171
  /**
157
- * @ignore
172
+ * Validate input data and execute route handler with appropriate middleware
173
+ *
174
+ * @param {Object} services - Available services
175
+ * @param {Object} matchedRoute - Information about the matched route
176
+ * @param {PikkuHTTP | undefined} http - HTTP interaction object
177
+ * @param {Object} options - Additional options
178
+ * @returns {Promise<any>} Result of the route handler
158
179
  */
159
- export const runHTTPRoute = async ({ singletonServices, request, response, createSessionServices, route: apiRoute, method: apiType, skipUserSession = false, respondWith404 = true, logWarningsForStatusCodes = [], coerceToArray = false, bubbleErrors = false, }) => {
180
+ const executeRouteWithMiddleware = async (services, matchedRoute, http, options) => {
181
+ const { matchedPath, params, route, middleware, schemaName } = matchedRoute;
182
+ const { singletonServices, userSessionService, createSessionServices, skipUserSession, } = services;
183
+ const { coerceToArray } = options;
184
+ const requiresSession = route.auth !== false;
160
185
  let sessionServices;
161
- const trackerId = crypto.randomUUID().toString();
162
- const http = createHTTPInteraction(request, response);
163
- const matchedRoute = getMatchingRoute(singletonServices.schemaService, apiType, apiRoute);
164
- try {
165
- if (!matchedRoute) {
186
+ let result;
187
+ http?.request?.setParams(params);
188
+ singletonServices.logger.info(`Matched route: ${route.route} | method: ${route.method.toUpperCase()} | auth: ${requiresSession.toString()}`);
189
+ // Main route execution function
190
+ const runMain = async () => {
191
+ const session = userSessionService.get();
192
+ // Validate session was set if needed
193
+ if (skipUserSession && requiresSession) {
194
+ throw new Error("Can't skip trying to get user session if auth is required");
195
+ }
196
+ if (requiresSession && !session) {
166
197
  singletonServices.logger.info({
167
- message: 'Route not found',
168
- apiRoute,
169
- apiType,
198
+ action: 'Rejecting route (invalid session)',
199
+ path: matchedPath,
170
200
  });
171
- throw new NotFoundError(`Route not found: ${apiRoute}`);
201
+ throw new MissingSessionError();
172
202
  }
173
- const { matchedPath, params, route, schemaName } = matchedRoute;
174
- const requiresSession = route.auth !== false;
175
- http?.request?.setParams(params);
176
- singletonServices.logger.info(`Matched route: ${route.route} | method: ${route.method.toUpperCase()} | auth: ${requiresSession.toString()}`);
177
- const session = await loadUserSession(skipUserSession, requiresSession, http, matchedPath, route, singletonServices.logger, singletonServices.httpSessionService);
178
- const data = await request.getData();
179
- validateAndCoerce(singletonServices.logger, singletonServices.schemaService, schemaName, data, coerceToArray);
203
+ // Create session services
180
204
  sessionServices = await createSessionServices(singletonServices, { http }, session);
181
205
  const allServices = { ...singletonServices, ...sessionServices, http };
182
- if (singletonServices.enforceHTTPAccess) {
183
- await singletonServices.enforceHTTPAccess(route, session);
206
+ const data = await http?.request?.getData();
207
+ // Validate schema
208
+ validateSchema(singletonServices.logger, singletonServices.schemaService, schemaName, data);
209
+ if (coerceToArray && schemaName) {
210
+ coerceQueryStringToArray(schemaName, data);
184
211
  }
212
+ // Run permission checks
185
213
  const permissioned = await verifyPermissions(route.permissions, allServices, data, session);
186
214
  if (permissioned === false) {
187
215
  throw new ForbiddenError('Permission denied');
188
216
  }
189
- const result = (await route.func(allServices, data, session));
217
+ // Execute the route handler
218
+ result = await route.func(allServices, data, session);
219
+ // Set the response
190
220
  if (route.returnsJSON === false) {
191
221
  http?.response?.setResponse(result);
192
222
  }
@@ -196,11 +226,52 @@ export const runHTTPRoute = async ({ singletonServices, request, response, creat
196
226
  http?.response?.setStatus(200);
197
227
  http?.response?.end();
198
228
  return result;
229
+ };
230
+ await runMiddleware({ ...singletonServices, userSessionService }, { http }, middleware, runMain);
231
+ return sessionServices ? { result, sessionServices } : { result };
232
+ };
233
+ /**
234
+ * Run an HTTP route with the given parameters
235
+ *
236
+ * @param {Object} options - Options for running the route
237
+ * @returns {Promise<Out | void>} Result of the route handler
238
+ * @ignore
239
+ */
240
+ export const runHTTPRoute = async ({ singletonServices, request, response, createSessionServices, route: apiRoute, method: apiType, skipUserSession = false, respondWith404 = true, logWarningsForStatusCodes = [], coerceToArray = false, bubbleErrors = false, }) => {
241
+ const trackerId = crypto.randomUUID().toString();
242
+ const userSessionService = new LocalUserSessionService();
243
+ let sessionServices;
244
+ let result;
245
+ // Create HTTP interaction object
246
+ const http = createHTTPInteraction(request, response);
247
+ // Find matching route
248
+ const matchedRoute = getMatchingRoute(apiType, apiRoute);
249
+ try {
250
+ // Handle route not found
251
+ if (!matchedRoute) {
252
+ singletonServices.logger.info({
253
+ message: 'Route not found',
254
+ apiRoute,
255
+ apiType,
256
+ });
257
+ throw new NotFoundError(`Route not found: ${apiRoute}`);
258
+ }
259
+ // Execute route with middleware
260
+ ;
261
+ ({ result, sessionServices } = await executeRouteWithMiddleware({
262
+ singletonServices,
263
+ userSessionService,
264
+ createSessionServices,
265
+ skipUserSession,
266
+ }, matchedRoute, http, { coerceToArray }));
267
+ return result;
199
268
  }
200
269
  catch (e) {
270
+ // Handle and possibly bubble the error
201
271
  handleError(e, http, trackerId, singletonServices.logger, logWarningsForStatusCodes, respondWith404, bubbleErrors);
202
272
  }
203
273
  finally {
274
+ // Clean up session services
204
275
  if (sessionServices) {
205
276
  await closeSessionServices(singletonServices.logger, sessionServices);
206
277
  }
@@ -1,5 +1,5 @@
1
1
  import { PikkuError } from '../errors/error-handler.js';
2
- import { APIDocs, CoreServices, CoreSingletonServices, CoreUserSession, CreateSessionServices } from '../types/core.types.js';
2
+ import { APIDocs, CoreServices, CoreSingletonServices, CoreUserSession, CreateSessionServices, PikkuMiddleware } from '../types/core.types.js';
3
3
  import { CoreAPIFunction, CoreAPIFunctionSessionless, CoreAPIPermission } from '../types/functions.types.js';
4
4
  import { PikkuRequest } from '../pikku-request.js';
5
5
  import { PikkuResponse } from '../pikku-response.js';
@@ -65,18 +65,22 @@ export type PikkuQuery<T = Record<string, string | undefined>> = Record<string,
65
65
  * @template APIFunctionSessionless - The sessionless API function type, defaults to `CoreAPIFunctionSessionless`.
66
66
  * @template APIPermission - The permission function type, defaults to `CoreAPIPermission`.
67
67
  */
68
- export type CoreHTTPFunctionRoute<In, Out, R extends string, APIFunction = CoreAPIFunction<In, Out>, APIFunctionSessionless = CoreAPIFunctionSessionless<In, Out>, APIPermission = CoreAPIPermission<In>> = (CoreHTTPFunction & {
68
+ export type CoreHTTPFunctionRoute<In, Out, R extends string, APIFunction = CoreAPIFunction<In, Out>, APIFunctionSessionless = CoreAPIFunctionSessionless<In, Out>, APIPermission = CoreAPIPermission<In>, APIMiddleware = PikkuMiddleware> = (CoreHTTPFunction & {
69
69
  route: R;
70
70
  method: HTTPMethod;
71
71
  func: APIFunction;
72
72
  permissions?: Record<string, APIPermission[] | APIPermission>;
73
73
  auth?: true;
74
+ tags?: string[];
75
+ middleware?: APIMiddleware[];
74
76
  }) | (CoreHTTPFunction & {
75
77
  route: R;
76
78
  method: HTTPMethod;
77
79
  func: APIFunctionSessionless;
78
80
  permissions?: undefined;
79
81
  auth?: false;
82
+ tags?: string[];
83
+ middleware?: APIMiddleware[];
80
84
  }) | (CoreHTTPFunction & {
81
85
  route: R;
82
86
  method: 'post';
@@ -84,6 +88,8 @@ export type CoreHTTPFunctionRoute<In, Out, R extends string, APIFunction = CoreA
84
88
  permissions?: Record<string, APIPermission[] | APIPermission>;
85
89
  auth?: true;
86
90
  query?: Array<keyof In>;
91
+ tags?: string[];
92
+ middleware?: APIMiddleware[];
87
93
  }) | (CoreHTTPFunction & {
88
94
  route: R;
89
95
  method: 'post';
@@ -91,6 +97,8 @@ export type CoreHTTPFunctionRoute<In, Out, R extends string, APIFunction = CoreA
91
97
  permissions?: undefined;
92
98
  auth?: false;
93
99
  query?: Array<keyof In>;
100
+ tags?: string[];
101
+ middleware?: APIMiddleware[];
94
102
  });
95
103
  /**
96
104
  * Represents an array of core API routes.
@@ -116,12 +124,10 @@ export type HTTPRoutesMeta = Array<{
116
124
  output: string | null;
117
125
  inputTypes?: HTTPFunctionMetaInputTypes;
118
126
  docs?: APIDocs;
127
+ tags?: string[];
119
128
  }>;
120
- /**
121
- * Verifies access to a route.
122
- * @param route - The route to verify access for.
123
- * @param session - The user session.
124
- * @returns A promise that resolves if access is granted.
125
- */
126
- export type enforceHTTPAccess = (route: CoreHTTPFunctionRoute<unknown, unknown, any>, session?: CoreUserSession) => Promise<void> | void;
129
+ export type HTTPRouteMiddleware = {
130
+ route: string;
131
+ middleware: PikkuMiddleware[];
132
+ };
127
133
  export {};
@@ -1,4 +1,3 @@
1
- export * from './pikku-http-session-service.js';
2
1
  export * from './pikku-http-abstract-request.js';
3
2
  export * from './pikku-http-abstract-response.js';
4
3
  export * from './log-http-routes.js';
@@ -1,4 +1,3 @@
1
- export * from './pikku-http-session-service.js';
2
1
  export * from './pikku-http-abstract-request.js';
3
2
  export * from './pikku-http-abstract-response.js';
4
3
  export * from './log-http-routes.js';
@@ -1,12 +1,15 @@
1
1
  import { PikkuRequest } from '../pikku-request.js';
2
- import { PikkuQuery } from './http-routes.types.js';
2
+ import { HTTPMethod, PikkuQuery } from './http-routes.types.js';
3
3
  /**
4
4
  * Abstract class representing a pikku request.
5
5
  * @template In - The type of the request body.
6
6
  * @group RequestResponse
7
7
  */
8
8
  export declare abstract class PikkuHTTPAbstractRequest<In = unknown> extends PikkuRequest<In> {
9
+ path: string;
10
+ method: HTTPMethod;
9
11
  private params;
12
+ constructor(path: string, method: HTTPMethod);
10
13
  /**
11
14
  * Retrieves the request body.
12
15
  * @returns A promise that resolves to the request body.
@@ -6,7 +6,14 @@ import { PikkuRequest } from '../pikku-request.js';
6
6
  * @group RequestResponse
7
7
  */
8
8
  export class PikkuHTTPAbstractRequest extends PikkuRequest {
9
+ path;
10
+ method;
9
11
  params = {};
12
+ constructor(path, method) {
13
+ super();
14
+ this.path = path;
15
+ this.method = method;
16
+ }
10
17
  /**
11
18
  * Retrieves the raw request body as a Buffer.
12
19
  * @returns A promise that resolves to the raw request body.
package/dist/index.d.ts CHANGED
@@ -9,6 +9,8 @@ export * from './services/index.js';
9
9
  export * from './http/index.js';
10
10
  export * from './channel/index.js';
11
11
  export * from './scheduler/index.js';
12
+ export * from './errors/index.js';
13
+ export * from './middleware/index.js';
12
14
  export { addRoute } from './http/http-route-runner.js';
13
15
  export { addChannel } from './channel/channel-runner.js';
14
16
  export { addScheduledTask } from './scheduler/scheduler-runner.js';
package/dist/index.js CHANGED
@@ -9,6 +9,8 @@ export * from './services/index.js';
9
9
  export * from './http/index.js';
10
10
  export * from './channel/index.js';
11
11
  export * from './scheduler/index.js';
12
+ export * from './errors/index.js';
13
+ export * from './middleware/index.js';
12
14
  export { addRoute } from './http/http-route-runner.js';
13
15
  export { addChannel } from './channel/channel-runner.js';
14
16
  export { addScheduledTask } from './scheduler/scheduler-runner.js';
@@ -0,0 +1,9 @@
1
+ import { PikkuMiddleware } from '../types/core.types.js';
2
+ /**
3
+ * API key middleware that retrieves a session from the 'x-api-key' header using a provided callback.
4
+ *
5
+ * @param options.getSessionForAPIKey - A function that returns a session when provided an API key.
6
+ */
7
+ export declare const apiKeyMiddleware: (options?: {
8
+ getSessionForAPIKey?: (apiKey: string) => Promise<any>;
9
+ }) => PikkuMiddleware;
@@ -0,0 +1,23 @@
1
+ /**
2
+ * API key middleware that retrieves a session from the 'x-api-key' header using a provided callback.
3
+ *
4
+ * @param options.getSessionForAPIKey - A function that returns a session when provided an API key.
5
+ */
6
+ export const apiKeyMiddleware = (options = {}) => {
7
+ const middleware = async ({ userSessionService }, { http }, next) => {
8
+ if (!http?.request || userSessionService.get()) {
9
+ return next();
10
+ }
11
+ if (options.getSessionForAPIKey) {
12
+ const apiKey = http.request.getHeader('x-api-key');
13
+ if (apiKey) {
14
+ const userSession = await options.getSessionForAPIKey(apiKey);
15
+ if (userSession) {
16
+ await userSessionService.set(userSession);
17
+ }
18
+ }
19
+ }
20
+ return next();
21
+ };
22
+ return middleware;
23
+ };
@@ -0,0 +1,11 @@
1
+ import { PikkuMiddleware } from '../types/core.types.js';
2
+ /**
3
+ * Cookie middleware that extracts a session from cookies.
4
+ *
5
+ * @param options.cookieNames - List of cookie names to check.
6
+ * @param options.getSessionForCookieValue - Function to retrieve a session using a cookie value.
7
+ */
8
+ export declare const cookieMiddleware: (options?: {
9
+ cookieNames?: string[];
10
+ getSessionForCookieValue?: (cookieValue: string, cookieName: string) => Promise<any>;
11
+ }) => PikkuMiddleware;
@@ -0,0 +1,36 @@
1
+ /**
2
+ * Cookie middleware that extracts a session from cookies.
3
+ *
4
+ * @param options.cookieNames - List of cookie names to check.
5
+ * @param options.getSessionForCookieValue - Function to retrieve a session using a cookie value.
6
+ */
7
+ export const cookieMiddleware = (options = {}) => {
8
+ const middleware = async ({ userSessionService }, { http }, next) => {
9
+ if (!http?.request || userSessionService.get()) {
10
+ return next();
11
+ }
12
+ if (options.getSessionForCookieValue && options.cookieNames) {
13
+ const cookies = http.request.getCookies();
14
+ if (cookies) {
15
+ let cookieName;
16
+ for (const name of options.cookieNames) {
17
+ if (cookies[name]) {
18
+ cookieName = name;
19
+ break;
20
+ }
21
+ }
22
+ if (cookieName) {
23
+ const cookieValue = cookies[cookieName];
24
+ if (cookieValue) {
25
+ const userSession = await options.getSessionForCookieValue(cookieValue, cookieName);
26
+ if (userSession) {
27
+ await userSessionService.set(userSession);
28
+ }
29
+ }
30
+ }
31
+ }
32
+ }
33
+ return next();
34
+ };
35
+ return middleware;
36
+ };
@@ -0,0 +1,10 @@
1
+ import { PikkuMiddleware } from '../types/core.types.js';
2
+ /**
3
+ * JWT middleware that extracts the Bearer token from the Authorization header,
4
+ * decodes it via the jwtService, and sets the session if found.
5
+ *
6
+ * @param options.debugJWTDecode - Optional flag for debugging the JWT decode process.
7
+ */
8
+ export declare const jwtMiddleware: (options?: {
9
+ debugJWTDecode?: boolean;
10
+ }) => PikkuMiddleware;
@@ -0,0 +1,32 @@
1
+ import { InvalidSessionError } from '../errors/errors.js';
2
+ /**
3
+ * JWT middleware that extracts the Bearer token from the Authorization header,
4
+ * decodes it via the jwtService, and sets the session if found.
5
+ *
6
+ * @param options.debugJWTDecode - Optional flag for debugging the JWT decode process.
7
+ */
8
+ export const jwtMiddleware = (options = {}) => {
9
+ const middleware = async ({ jwt, userSessionService }, { http }, next) => {
10
+ if (!jwt) {
11
+ throw new Error('JWT service not found');
12
+ }
13
+ // Skip if session already exists.
14
+ if (!http?.request || userSessionService.get()) {
15
+ return next();
16
+ }
17
+ const authHeader = http.request.getHeader('authorization') ||
18
+ http.request.getHeader('Authorization');
19
+ if (authHeader) {
20
+ const [scheme, token] = authHeader.split(' ');
21
+ if (scheme !== 'Bearer' || !token) {
22
+ throw new InvalidSessionError();
23
+ }
24
+ const userSession = await jwt.decode(token, undefined, options.debugJWTDecode);
25
+ if (userSession) {
26
+ await userSessionService.set(userSession);
27
+ }
28
+ }
29
+ return next();
30
+ };
31
+ return middleware;
32
+ };
@@ -0,0 +1,10 @@
1
+ import { PikkuQuery } from '../http/http-routes.types.js';
2
+ import { PikkuMiddleware } from '../types/core.types.js';
3
+ /**
4
+ * Query middleware that retrieves a session using query parameters.
5
+ *
6
+ * @param options.getSessionForQueryValue - A function that returns a session based on query parameters.
7
+ */
8
+ export declare const queryMiddleware: (options?: {
9
+ getSessionForQueryValue?: (query: PikkuQuery) => Promise<any>;
10
+ }) => PikkuMiddleware;
@@ -0,0 +1,21 @@
1
+ /**
2
+ * Query middleware that retrieves a session using query parameters.
3
+ *
4
+ * @param options.getSessionForQueryValue - A function that returns a session based on query parameters.
5
+ */
6
+ export const queryMiddleware = (options = {}) => {
7
+ const middleware = async ({ userSessionService }, { http }, next) => {
8
+ if (!http?.request || userSessionService.get()) {
9
+ return next();
10
+ }
11
+ if (options.getSessionForQueryValue) {
12
+ const query = http.request.getQuery();
13
+ const userSession = await options.getSessionForQueryValue(query);
14
+ if (userSession) {
15
+ await userSessionService.set(userSession);
16
+ }
17
+ }
18
+ return next();
19
+ };
20
+ return middleware;
21
+ };