@twin.org/api-processors 0.0.1-next.8 → 0.0.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.
Files changed (33) hide show
  1. package/dist/cjs/index.cjs +212 -30
  2. package/dist/esm/index.mjs +211 -31
  3. package/dist/types/data/{routeProcessor.d.ts → restRouteProcessor.d.ts} +8 -8
  4. package/dist/types/data/socketRouteProcessor.d.ts +32 -0
  5. package/dist/types/identity/nodeIdentityProcessor.d.ts +7 -3
  6. package/dist/types/identity/staticUserIdentityProcessor.d.ts +9 -9
  7. package/dist/types/index.d.ts +9 -4
  8. package/dist/types/logging/loggingProcessor.d.ts +11 -13
  9. package/dist/types/mimeType/jwtMimeTypeProcessor.d.ts +25 -0
  10. package/dist/types/models/ILoggingProcessorConfig.d.ts +17 -0
  11. package/dist/types/models/ILoggingProcessorConstructorOptions.d.ts +15 -0
  12. package/dist/types/models/IRestRouteProcessorConstructorOptions.d.ts +10 -0
  13. package/dist/types/models/ISocketRouteProcessorConstructorOptions.d.ts +10 -0
  14. package/dist/types/models/IStaticUserIdentityProcessorConstructorOptions.d.ts +10 -0
  15. package/docs/changelog.md +85 -1
  16. package/docs/reference/classes/JwtMimeTypeProcessor.md +81 -0
  17. package/docs/reference/classes/LoggingProcessor.md +43 -27
  18. package/docs/reference/classes/NodeIdentityProcessor.md +35 -15
  19. package/docs/reference/classes/{RouteProcessor.md → RestRouteProcessor.md} +30 -18
  20. package/docs/reference/classes/SocketRouteProcessor.md +99 -0
  21. package/docs/reference/classes/StaticUserIdentityProcessor.md +29 -17
  22. package/docs/reference/index.md +8 -3
  23. package/docs/reference/interfaces/ILoggingProcessorConfig.md +27 -0
  24. package/docs/reference/interfaces/ILoggingProcessorConstructorOptions.md +23 -0
  25. package/docs/reference/interfaces/IRestRouteProcessorConstructorOptions.md +11 -0
  26. package/docs/reference/interfaces/ISocketRouteProcessorConstructorOptions.md +11 -0
  27. package/docs/reference/interfaces/IStaticUserIdentityProcessorConstructorOptions.md +11 -0
  28. package/locales/en.json +5 -2
  29. package/package.json +10 -10
  30. package/dist/types/models/IRequestLoggingProcessorConfig.d.ts +0 -9
  31. package/dist/types/models/IResponseLoggingProcessorConfig.d.ts +0 -9
  32. package/docs/reference/interfaces/IRequestLoggingProcessorConfig.md +0 -11
  33. package/docs/reference/interfaces/IResponseLoggingProcessorConfig.md +0 -11
@@ -10,11 +10,15 @@ var loggingModels = require('@twin.org/logging-models');
10
10
  /**
11
11
  * Process the REST request and hands it on to the route handler.
12
12
  */
13
- class RouteProcessor {
13
+ class RestRouteProcessor {
14
+ /**
15
+ * The namespace supported by the processor.
16
+ */
17
+ static NAMESPACE = "rest-route";
14
18
  /**
15
19
  * Runtime name for the class.
16
20
  */
17
- CLASS_NAME = "RouteProcessor";
21
+ CLASS_NAME = "RestRouteProcessor";
18
22
  /**
19
23
  * Include the stack with errors.
20
24
  * @internal
@@ -23,8 +27,6 @@ class RouteProcessor {
23
27
  /**
24
28
  * Create a new instance of RouteProcessor.
25
29
  * @param options Options for the processor.
26
- * @param options.config The configuration for the processor.
27
- * @returns Promise that resolves when the processor is initialized.
28
30
  */
29
31
  constructor(options) {
30
32
  this._includeErrorStack = options?.config?.includeErrorStack ?? false;
@@ -112,42 +114,78 @@ class RouteProcessor {
112
114
  }
113
115
  }
114
116
 
117
+ // Copyright 2024 IOTA Stiftung.
118
+ // SPDX-License-Identifier: Apache-2.0.
115
119
  /**
116
- * Adds a static user identity to the request context.
120
+ * Process the socket request and hands it on to the route handler.
117
121
  */
118
- class StaticUserIdentityProcessor {
122
+ class SocketRouteProcessor {
123
+ /**
124
+ * The namespace supported by the processor.
125
+ */
126
+ static NAMESPACE = "socket-route";
119
127
  /**
120
128
  * Runtime name for the class.
121
129
  */
122
- CLASS_NAME = "StaticUserIdentityProcessor";
130
+ CLASS_NAME = "SocketRouteProcessor";
123
131
  /**
124
- * The fixed identity for request context.
132
+ * Include the stack with errors.
125
133
  * @internal
126
134
  */
127
- _userIdentity;
135
+ _includeErrorStack;
128
136
  /**
129
- * Create a new instance of StaticIdentityProcessor.
137
+ * Create a new instance of SocketRouteProcessor.
130
138
  * @param options Options for the processor.
131
- * @param options.config The configuration for the processor.
132
- * @returns Promise that resolves when the processor is initialized.
133
139
  */
134
140
  constructor(options) {
135
- core.Guards.object(this.CLASS_NAME, "options", options);
136
- core.Guards.object(this.CLASS_NAME, "options.config", options.config);
137
- core.Guards.stringValue(this.CLASS_NAME, "options.config.userIdentity", options.config.userIdentity);
138
- this._userIdentity = options.config.userIdentity;
141
+ this._includeErrorStack = options?.config?.includeErrorStack ?? false;
139
142
  }
140
143
  /**
141
- * Pre process the REST request for the specified route.
144
+ * Process the REST request for the specified route.
142
145
  * @param request The incoming request.
143
146
  * @param response The outgoing response.
144
147
  * @param route The route to process.
145
148
  * @param requestIdentity The identity context for the request.
146
149
  * @param processorState The state handed through the processors.
150
+ * @param responseEmitter The function to emit a response.
147
151
  */
148
- async pre(request, response, route, requestIdentity, processorState) {
149
- if (!core.Is.empty(route) && !(route.skipAuth ?? false)) {
150
- requestIdentity.userIdentity = this._userIdentity;
152
+ async process(request, response, route, requestIdentity, processorState, responseEmitter) {
153
+ // Don't handle the route if another processor has already set the response
154
+ // status code e.g. from an auth processor
155
+ if (core.Is.empty(response.statusCode)) {
156
+ if (core.Is.empty(route)) {
157
+ apiModels.HttpErrorHelper.buildResponse(response, {
158
+ name: core.NotFoundError.CLASS_NAME,
159
+ message: `${this.CLASS_NAME}.routeNotFound`,
160
+ properties: {
161
+ notFoundId: request.url
162
+ }
163
+ }, web.HttpStatusCode.notFound);
164
+ }
165
+ else {
166
+ try {
167
+ const req = {
168
+ pathParams: request.pathParams,
169
+ query: request.query,
170
+ body: request.body
171
+ };
172
+ await route.handler({
173
+ ...requestIdentity,
174
+ serverRequest: request,
175
+ processorState
176
+ }, req, async (topic, restRouteResponse) => {
177
+ response.headers = restRouteResponse?.headers;
178
+ response.body = restRouteResponse?.body;
179
+ response.statusCode =
180
+ restRouteResponse.statusCode ?? response.statusCode ?? web.HttpStatusCode.ok;
181
+ await responseEmitter(topic, response);
182
+ });
183
+ }
184
+ catch (err) {
185
+ const { error, httpStatusCode } = apiModels.HttpErrorHelper.processError(err, this._includeErrorStack);
186
+ apiModels.HttpErrorHelper.buildResponse(response, error, httpStatusCode);
187
+ }
188
+ }
151
189
  }
152
190
  }
153
191
  }
@@ -156,6 +194,10 @@ class StaticUserIdentityProcessor {
156
194
  * Adds a node identity to the request identity.
157
195
  */
158
196
  class NodeIdentityProcessor {
197
+ /**
198
+ * The namespace supported by the processor.
199
+ */
200
+ static NAMESPACE = "node-identity";
159
201
  /**
160
202
  * Runtime name for the class.
161
203
  */
@@ -188,10 +230,56 @@ class NodeIdentityProcessor {
188
230
  }
189
231
  }
190
232
 
233
+ /**
234
+ * Adds a static user identity to the request context.
235
+ */
236
+ class StaticUserIdentityProcessor {
237
+ /**
238
+ * The namespace supported by the processor.
239
+ */
240
+ static NAMESPACE = "static-user-identity";
241
+ /**
242
+ * Runtime name for the class.
243
+ */
244
+ CLASS_NAME = "StaticUserIdentityProcessor";
245
+ /**
246
+ * The fixed identity for request context.
247
+ * @internal
248
+ */
249
+ _userIdentity;
250
+ /**
251
+ * Create a new instance of StaticIdentityProcessor.
252
+ * @param options Options for the processor.
253
+ */
254
+ constructor(options) {
255
+ core.Guards.object(this.CLASS_NAME, "options", options);
256
+ core.Guards.object(this.CLASS_NAME, "options.config", options.config);
257
+ core.Guards.stringValue(this.CLASS_NAME, "options.config.userIdentity", options.config.userIdentity);
258
+ this._userIdentity = options.config.userIdentity;
259
+ }
260
+ /**
261
+ * Pre process the REST request for the specified route.
262
+ * @param request The incoming request.
263
+ * @param response The outgoing response.
264
+ * @param route The route to process.
265
+ * @param requestIdentity The identity context for the request.
266
+ * @param processorState The state handed through the processors.
267
+ */
268
+ async pre(request, response, route, requestIdentity, processorState) {
269
+ if (!core.Is.empty(route) && !(route.skipAuth ?? false)) {
270
+ requestIdentity.userIdentity = this._userIdentity;
271
+ }
272
+ }
273
+ }
274
+
191
275
  /**
192
276
  * Process the REST request and log its information.
193
277
  */
194
278
  class LoggingProcessor {
279
+ /**
280
+ * The namespace supported by the processor.
281
+ */
282
+ static NAMESPACE = "logging";
195
283
  /**
196
284
  * Runtime name for the class.
197
285
  */
@@ -207,15 +295,24 @@ class LoggingProcessor {
207
295
  */
208
296
  _includeBody;
209
297
  /**
210
- * Create a new instance of RequestLoggingProcessor.
298
+ * Show the full base64 content for data, default to abbreviate.
299
+ * @internal
300
+ */
301
+ _fullBase64;
302
+ /**
303
+ * List of property names to obfuscate, defaults to "password".
304
+ * @internal
305
+ */
306
+ _obfuscateProperties;
307
+ /**
308
+ * Create a new instance of LoggingProcessor.
211
309
  * @param options Options for the processor.
212
- * @param options.loggingConnectorType The type for the logging connector, defaults to "logging".
213
- * @param options.config The configuration for the processor.
214
- * @returns Promise that resolves when the processor is initialized.
215
310
  */
216
311
  constructor(options) {
217
312
  this._loggingConnector = loggingModels.LoggingConnectorFactory.get(options?.loggingConnectorType ?? "logging");
218
313
  this._includeBody = options?.config?.includeBody ?? false;
314
+ this._fullBase64 = options?.config?.fullBase64 ?? false;
315
+ this._obfuscateProperties = options?.config?.obfuscateProperties ?? ["password"];
219
316
  }
220
317
  /**
221
318
  * Pre process the REST request for the specified route.
@@ -228,12 +325,28 @@ class LoggingProcessor {
228
325
  async pre(request, response, route, requestIdentity, processorState) {
229
326
  const now = process.hrtime.bigint();
230
327
  processorState.requestStart = now;
328
+ const contentType = request.headers?.[web.HeaderTypes.ContentType];
329
+ const isJson = core.Is.stringValue(contentType)
330
+ ? contentType.includes(web.MimeTypes.Json) || contentType.includes(web.MimeTypes.JsonLd)
331
+ : false;
332
+ let requestUrl = "";
333
+ if (core.Is.stringValue(request.url)) {
334
+ // Socket paths do not have a prefix so just use the whole url.
335
+ if (request.url.startsWith("http")) {
336
+ requestUrl = new URL(request.url).pathname;
337
+ }
338
+ else {
339
+ requestUrl = request.url;
340
+ }
341
+ }
231
342
  await this._loggingConnector.log({
232
343
  level: "info",
233
344
  source: this.CLASS_NAME,
234
345
  ts: Date.now(),
235
- message: `===> ${request.method} ${request.url ? new URL(request.url).pathname : ""}`,
236
- data: this._includeBody ? request?.body : undefined
346
+ message: `===> ${request.method} ${requestUrl}`,
347
+ data: this._includeBody && isJson
348
+ ? this.processJson("body", core.ObjectHelper.clone(request?.body))
349
+ : undefined
237
350
  });
238
351
  }
239
352
  /**
@@ -248,11 +361,13 @@ class LoggingProcessor {
248
361
  let data;
249
362
  if (this._includeBody) {
250
363
  const contentType = response.headers?.[web.HeaderTypes.ContentType];
251
- const isJson = contentType?.includes(`${web.MimeTypes.Json}; charset=utf-8`);
364
+ const isJson = core.Is.stringValue(contentType)
365
+ ? contentType.includes(web.MimeTypes.Json) || contentType.includes(web.MimeTypes.JsonLd)
366
+ : false;
252
367
  const contentLength = response.headers?.[web.HeaderTypes.ContentLength];
253
368
  if (isJson) {
254
369
  data = {
255
- body: response.body
370
+ body: this.processJson("body", core.ObjectHelper.clone(response.body))
256
371
  };
257
372
  }
258
373
  else {
@@ -274,19 +389,86 @@ class LoggingProcessor {
274
389
  const start = core.Coerce.bigint(processorState.requestStart) ?? now;
275
390
  const elapsed = now - start;
276
391
  const elapsedMicroSeconds = Math.floor(Number(elapsed) / 1000);
392
+ let requestUrl = "";
393
+ if (core.Is.stringValue(request.url)) {
394
+ // Socket paths do not have a prefix so just use the whole url.
395
+ if (request.url.startsWith("http")) {
396
+ requestUrl = new URL(request.url).pathname;
397
+ }
398
+ else {
399
+ requestUrl = request.url;
400
+ }
401
+ }
277
402
  await this._loggingConnector.log({
278
403
  level: core.Is.number(response.statusCode) && response.statusCode >= web.HttpStatusCode.badRequest
279
404
  ? "error"
280
405
  : "info",
281
406
  source: this.CLASS_NAME,
282
407
  ts: Date.now(),
283
- message: `<=== ${response.statusCode ?? ""} ${request.method} ${request.url ? new URL(request.url).pathname : ""} duration: ${elapsedMicroSeconds}µs`,
408
+ message: `<=== ${response.statusCode ?? ""} ${request.method} ${requestUrl} duration: ${elapsedMicroSeconds}µs`,
284
409
  data
285
410
  });
286
411
  }
412
+ /**
413
+ * Process the JSON.
414
+ * @param propValue The property to process.
415
+ * @returns The processed property.
416
+ * @internal
417
+ */
418
+ processJson(propName, propValue) {
419
+ if (core.Is.array(propValue)) {
420
+ for (let i = 0; i < propValue.length; i++) {
421
+ propValue[i] = this.processJson(`${i}`, propValue[i]);
422
+ }
423
+ }
424
+ else if (core.Is.object(propValue)) {
425
+ for (const key of Object.keys(propValue)) {
426
+ propValue[key] = this.processJson(key, propValue[key]);
427
+ }
428
+ }
429
+ else if (!this._fullBase64 && core.Is.stringBase64(propValue) && propValue.length > 256) {
430
+ propValue = "<base64>";
431
+ }
432
+ else if (core.Is.string(propValue) &&
433
+ this._obfuscateProperties.some(op => new RegExp(op).test(propName))) {
434
+ propValue = "**************";
435
+ }
436
+ return propValue;
437
+ }
438
+ }
439
+
440
+ /**
441
+ * Process the JWT mime type.
442
+ */
443
+ class JwtMimeTypeProcessor {
444
+ /**
445
+ * The namespace supported by the processor.
446
+ */
447
+ static NAMESPACE = "jwt";
448
+ /**
449
+ * Runtime name for the class.
450
+ */
451
+ CLASS_NAME = "JwtMimeTypeProcessor";
452
+ /**
453
+ * Get the MIME types that this handler can handle.
454
+ * @returns The MIME types that this handler can handle.
455
+ */
456
+ getTypes() {
457
+ return [web.MimeTypes.Jwt];
458
+ }
459
+ /**
460
+ * Handle content.
461
+ * @param body The body to process.
462
+ * @returns The processed body.
463
+ */
464
+ async handle(body) {
465
+ return core.Converter.bytesToUtf8(body);
466
+ }
287
467
  }
288
468
 
469
+ exports.JwtMimeTypeProcessor = JwtMimeTypeProcessor;
289
470
  exports.LoggingProcessor = LoggingProcessor;
290
471
  exports.NodeIdentityProcessor = NodeIdentityProcessor;
291
- exports.RouteProcessor = RouteProcessor;
472
+ exports.RestRouteProcessor = RestRouteProcessor;
473
+ exports.SocketRouteProcessor = SocketRouteProcessor;
292
474
  exports.StaticUserIdentityProcessor = StaticUserIdentityProcessor;