@yourrentals/cloudevent-receiver-nestjs 0.4.2 → 0.5.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.
package/README.md CHANGED
@@ -10,26 +10,57 @@ yarn add @yourrentals/cloudevent-receiver-nestjs @golevelup/nestjs-discovery
10
10
 
11
11
  ## Usage
12
12
 
13
+ This plugin requires [NestJS Raw Body parsing](https://docs.nestjs.com/faq/raw-body) for signature verification, and
14
+ CloudEvent Body Parser for CloudEvent parsing (in structured mode)
15
+
13
16
  ```ts
14
- import {CloudEventReceiverModule, CloudEventHandler} from '@yourrentals/cloudevent-receiver-nestjs'
15
- import {RsaVerifier} from '@yourrentals/message-bus-signature-rsa'
17
+ import { NestFactory } from '@nestjs/core';
18
+ import type { NestExpressApplication } from '@nestjs/platform-express';
19
+ import { AppModule } from './app.module';
20
+
21
+ // in the "bootstrap" function
22
+ const app = await NestFactory.create<NestExpressApplication>(AppModule, {
23
+ rawBody: true,
24
+ });
25
+
26
+ // Support CloudEvent - Structured mode parsing
27
+ app.useBodyParser('json', {
28
+ type: 'application/cloudevents+json',
29
+ });
30
+
31
+ await app.listen(3000);
32
+
33
+ ```
34
+
35
+ ```ts
36
+ import {
37
+ CloudEventReceiverModule,
38
+ EventHandler,
39
+ WebhookSignatureV1HmacPlugin,
40
+ CloudEvent
41
+ } from '@yourrentals/cloudevent-receiver-nestjs'
16
42
 
17
43
  @Injectable()
18
44
  class MessagebusHandlerService {
19
- @CloudEventHandler('my-queue')
20
- async handleMyQueue(event: CloudEvent) {
21
- // ...
22
- }
45
+ @EventHandler('my-queue')
46
+ async handleMyQueue(event: CloudEvent) {
47
+ // ...
48
+ }
49
+
50
+ @EventHandler('my-other-queue', false /* event is not CloudEvent */)
51
+ async handleSqsQueue(event: Message) {
52
+ // ...
53
+ }
23
54
  }
24
55
 
25
56
  @Module({
26
- imports: [
27
- CloudEventReceiverModule.forRoot({
28
- pathPrefix: '/message-bus',
29
- verifiers: [new RsaVerifier('signature', 'publicKey')],
30
- }),
31
- ],
32
- providers: [MessagebusHandlerService],
57
+ imports: [
58
+ CloudEventReceiverModule.forRoot({
59
+ pathPrefix: '/message-bus',
60
+ verifierPlugins: [new WebhookSignatureV1HmacPlugin('secret')],
61
+ }),
62
+ ],
63
+ providers: [MessagebusHandlerService],
33
64
  })
34
65
 
35
66
  ```
@@ -38,32 +69,84 @@ class MessagebusHandlerService {
38
69
  import Axios from 'axios';
39
70
 
40
71
  const main = async () => {
41
- // Queue name defined in the receiver are automatically registered
42
- await Axios.post('http://localhost:3000/message-bus/my-queue', {
43
- headers: {
44
- 'ce-specversion': '1.0',
45
- 'ce-type': 'my-event',
46
- 'ce-source': 'my-source',
47
- 'ce-id': 'my-id',
48
- 'ce-time': '2020-01-01T00:00:00Z',
49
- 'ce-datacontenttype': 'application/json',
50
- 'ce-dataschema': 'http://myschema.com',
51
- 'ce-subject': 'my-subject',
52
- 'ce-signature': 'my-signature',
53
- },
54
- body: {
55
- foo: 'bar',
56
- },
57
- });
72
+ // Queue name defined in the receiver are automatically registered
73
+ await Axios.post('http://localhost:3000/message-bus/my-queue', {
74
+ headers: {
75
+ 'ce-specversion': '1.0',
76
+ 'ce-type': 'my-event',
77
+ 'ce-source': 'my-source',
78
+ 'ce-id': 'my-id',
79
+ 'ce-time': '2020-01-01T00:00:00Z',
80
+ 'ce-datacontenttype': 'application/json',
81
+ 'ce-dataschema': 'http://myschema.com',
82
+ 'ce-subject': 'my-subject',
83
+ 'ce-signature': 'my-signature',
84
+ },
85
+ body: {
86
+ foo: 'bar',
87
+ },
88
+ });
58
89
  };
59
90
  ```
60
91
 
92
+ ## Authentication
93
+
94
+ NestJS usually have global authentication that will impede operations of this module, as the routes are considered public route. To disable authentication for the routes, you can use the `routeDecorator` module option:
95
+
96
+ ```ts
97
+ import { Module, SetMetadata } from '@nestjs/common';
98
+ import { AppController } from './app.controller';
99
+ import { AppService } from './app.service';
100
+ import {
101
+ CloudEventReceiverModule,
102
+ WebhookSignatureV1HmacPlugin,
103
+ } from '@yourrentals/cloudevent-receiver-nestjs';
104
+ import { APP_GUARD } from '@nestjs/core';
105
+ import { AuthGuardGuard } from './auth-guard/auth-guard.guard';
106
+
107
+ @Module({
108
+ imports: [
109
+ CloudEventReceiverModule.forRoot({
110
+ // Set the route decorator so that your authentication guard will treat the route as public
111
+ routeDecorator: SetMetadata('isPublic', true),
112
+ verifierPlugins: [new WebhookSignatureV1HmacPlugin('secret2')],
113
+ }),
114
+ ],
115
+ controllers: [AppController],
116
+ providers: [AppService, { provide: APP_GUARD, useClass: AuthGuardGuard }],
117
+ })
118
+ export class AppModule {}
119
+ ```
120
+
121
+ ## Raw Mode
122
+
123
+ The library can receive messages from any sources so long that it implements the Webhook Signature.
124
+ To disable parsing as cloudevent, set the `isCloudEvent` option in decorator to `false`.
125
+
126
+ By default, the event type is assumed to be a SQS Message
127
+
128
+ ```ts
129
+ import { Injectable } from '@nestjs/common';
130
+ import {
131
+ EventHandler,
132
+ SqsMessage,
133
+ } from '@yourrentals/cloudevent-receiver-nestjs';
134
+
135
+ @Injectable()
136
+ export class AppService {
137
+ @EventHandler('test', /* isCloudEvent */ false)
138
+ async handle(event: SqsMessage) {
139
+ console.log(event);
140
+ }
141
+ }
142
+ ```
143
+
61
144
  ## Errors
62
145
 
63
146
  The library defines the following errors:
64
147
 
65
148
  | Error | Description | Expected HTTP status code | Retryable |
66
- | ---------------------- | --------------------------------------- | ------------------------- | --------- |
149
+ |------------------------|-----------------------------------------|---------------------------|-----------|
67
150
  | InvalidSignatureError | The signature of the message is invalid | 401 | No |
68
151
  | UnparsableMessageError | The message is not a valid CloudEvent | 400 | No |
69
152
  | NotFoundError | The queue name is not registered | 404 | Yes |
package/index.cjs.js CHANGED
@@ -52,7 +52,7 @@ typeof SuppressedError === "function" ? SuppressedError : function (error, suppr
52
52
  return e.name = "SuppressedError", e.error = error, e.suppressed = suppressed, e;
53
53
  };
54
54
 
55
- const CLOUD_EVENT_HANDLER = Symbol.for('CLOUD_EVENT_HANDLER');
55
+ const EVENT_HANDLER = Symbol.for('EVENT_HANDLER');
56
56
  const CLOUD_EVENT_RECEIVER_OPTIONS = Symbol.for('CLOUD_EVENT_RECEIVER_OPTIONS');
57
57
 
58
58
  let CloudEventReceiverService = class CloudEventReceiverService {
@@ -62,13 +62,14 @@ let CloudEventReceiverService = class CloudEventReceiverService {
62
62
  }
63
63
  onModuleInit() {
64
64
  return __awaiter(this, void 0, void 0, function* () {
65
- const providers = yield this.discovery.providerMethodsWithMetaAtKey(CLOUD_EVENT_HANDLER);
65
+ const providers = yield this.discovery.providerMethodsWithMetaAtKey(EVENT_HANDLER);
66
66
  this.receiver = new cloudeventReceiverCore.CloudEventReceiver({
67
67
  queues: providers.map((provider) => ({
68
68
  name: provider.meta.name,
69
+ isCloudEvent: provider.meta.isCloudEvent,
69
70
  handler: provider.discoveredMethod.handler.bind(provider.discoveredMethod.parentClass.instance),
70
71
  })),
71
- verifiers: this.options.verifiers || [],
72
+ verifierPlugins: this.options.verifierPlugins || [],
72
73
  });
73
74
  });
74
75
  }
@@ -84,7 +85,7 @@ CloudEventReceiverService = __decorate([
84
85
  __metadata("design:paramtypes", [Object, nestjsDiscovery.DiscoveryService])
85
86
  ], CloudEventReceiverService);
86
87
 
87
- const createController = (pathPrefix = '/message-bus') => {
88
+ const createController = (pathPrefix = '/message-bus', routeDecorator = (target, propertyKey, descriptor) => descriptor) => {
88
89
  let CloudEventReceiverController = class CloudEventReceiverController {
89
90
  constructor(service) {
90
91
  this.service = service;
@@ -93,7 +94,7 @@ const createController = (pathPrefix = '/message-bus') => {
93
94
  return __awaiter(this, void 0, void 0, function* () {
94
95
  try {
95
96
  const queueName = request.params.queueName;
96
- yield this.service.handle(queueName, request.headers, request.body);
97
+ yield this.service.handle(queueName, request.headers, request.rawBody);
97
98
  }
98
99
  catch (e) {
99
100
  if (utilReceiverError.isReceiverError(e)) {
@@ -105,6 +106,7 @@ const createController = (pathPrefix = '/message-bus') => {
105
106
  }
106
107
  };
107
108
  __decorate([
109
+ routeDecorator,
108
110
  common.Post('/:queueName'),
109
111
  common.HttpCode(200),
110
112
  __param(0, common.Req()),
@@ -138,7 +140,9 @@ exports.CloudEventReceiverModule = CloudEventReceiverModule_1 = class CloudEvent
138
140
  inject: [CLOUD_EVENT_RECEIVER_OPTIONS, nestjsDiscovery.DiscoveryService],
139
141
  },
140
142
  ],
141
- controllers: [createController(options.pathPrefix)],
143
+ controllers: [
144
+ createController(options.pathPrefix, options.routeDecorator),
145
+ ],
142
146
  };
143
147
  }
144
148
  };
@@ -146,15 +150,9 @@ exports.CloudEventReceiverModule = CloudEventReceiverModule_1 = __decorate([
146
150
  common.Module({})
147
151
  ], exports.CloudEventReceiverModule);
148
152
 
149
- const CloudEventHandler = (queueName) => common.SetMetadata(CLOUD_EVENT_HANDLER, { name: queueName });
153
+ const EventHandler = (queueName, isCloudEvent = true) => common.SetMetadata(EVENT_HANDLER, { name: queueName, isCloudEvent });
150
154
 
151
- exports.CloudEventHandler = CloudEventHandler;
152
- Object.keys(utilReceiverError).forEach(function (k) {
153
- if (k !== 'default' && !exports.hasOwnProperty(k)) Object.defineProperty(exports, k, {
154
- enumerable: true,
155
- get: function () { return utilReceiverError[k]; }
156
- });
157
- });
155
+ exports.EventHandler = EventHandler;
158
156
  Object.keys(cloudeventReceiverCore).forEach(function (k) {
159
157
  if (k !== 'default' && !exports.hasOwnProperty(k)) Object.defineProperty(exports, k, {
160
158
  enumerable: true,
package/index.esm.js CHANGED
@@ -1,6 +1,5 @@
1
1
  import { Injectable, Inject, Post, HttpCode, Req, Controller, HttpException, InternalServerErrorException, Module, SetMetadata } from '@nestjs/common';
2
2
  import { isReceiverError } from '@yourrentals/util-receiver-error';
3
- export * from '@yourrentals/util-receiver-error';
4
3
  import { DiscoveryService, DiscoveryModule } from '@golevelup/nestjs-discovery';
5
4
  import { CloudEventReceiver } from '@yourrentals/cloudevent-receiver-core';
6
5
  export * from '@yourrentals/cloudevent-receiver-core';
@@ -50,7 +49,7 @@ typeof SuppressedError === "function" ? SuppressedError : function (error, suppr
50
49
  return e.name = "SuppressedError", e.error = error, e.suppressed = suppressed, e;
51
50
  };
52
51
 
53
- const CLOUD_EVENT_HANDLER = Symbol.for('CLOUD_EVENT_HANDLER');
52
+ const EVENT_HANDLER = Symbol.for('EVENT_HANDLER');
54
53
  const CLOUD_EVENT_RECEIVER_OPTIONS = Symbol.for('CLOUD_EVENT_RECEIVER_OPTIONS');
55
54
 
56
55
  let CloudEventReceiverService = class CloudEventReceiverService {
@@ -60,13 +59,14 @@ let CloudEventReceiverService = class CloudEventReceiverService {
60
59
  }
61
60
  onModuleInit() {
62
61
  return __awaiter(this, void 0, void 0, function* () {
63
- const providers = yield this.discovery.providerMethodsWithMetaAtKey(CLOUD_EVENT_HANDLER);
62
+ const providers = yield this.discovery.providerMethodsWithMetaAtKey(EVENT_HANDLER);
64
63
  this.receiver = new CloudEventReceiver({
65
64
  queues: providers.map((provider) => ({
66
65
  name: provider.meta.name,
66
+ isCloudEvent: provider.meta.isCloudEvent,
67
67
  handler: provider.discoveredMethod.handler.bind(provider.discoveredMethod.parentClass.instance),
68
68
  })),
69
- verifiers: this.options.verifiers || [],
69
+ verifierPlugins: this.options.verifierPlugins || [],
70
70
  });
71
71
  });
72
72
  }
@@ -82,7 +82,7 @@ CloudEventReceiverService = __decorate([
82
82
  __metadata("design:paramtypes", [Object, DiscoveryService])
83
83
  ], CloudEventReceiverService);
84
84
 
85
- const createController = (pathPrefix = '/message-bus') => {
85
+ const createController = (pathPrefix = '/message-bus', routeDecorator = (target, propertyKey, descriptor) => descriptor) => {
86
86
  let CloudEventReceiverController = class CloudEventReceiverController {
87
87
  constructor(service) {
88
88
  this.service = service;
@@ -91,7 +91,7 @@ const createController = (pathPrefix = '/message-bus') => {
91
91
  return __awaiter(this, void 0, void 0, function* () {
92
92
  try {
93
93
  const queueName = request.params.queueName;
94
- yield this.service.handle(queueName, request.headers, request.body);
94
+ yield this.service.handle(queueName, request.headers, request.rawBody);
95
95
  }
96
96
  catch (e) {
97
97
  if (isReceiverError(e)) {
@@ -103,6 +103,7 @@ const createController = (pathPrefix = '/message-bus') => {
103
103
  }
104
104
  };
105
105
  __decorate([
106
+ routeDecorator,
106
107
  Post('/:queueName'),
107
108
  HttpCode(200),
108
109
  __param(0, Req()),
@@ -136,7 +137,9 @@ let CloudEventReceiverModule = CloudEventReceiverModule_1 = class CloudEventRece
136
137
  inject: [CLOUD_EVENT_RECEIVER_OPTIONS, DiscoveryService],
137
138
  },
138
139
  ],
139
- controllers: [createController(options.pathPrefix)],
140
+ controllers: [
141
+ createController(options.pathPrefix, options.routeDecorator),
142
+ ],
140
143
  };
141
144
  }
142
145
  };
@@ -144,6 +147,6 @@ CloudEventReceiverModule = CloudEventReceiverModule_1 = __decorate([
144
147
  Module({})
145
148
  ], CloudEventReceiverModule);
146
149
 
147
- const CloudEventHandler = (queueName) => SetMetadata(CLOUD_EVENT_HANDLER, { name: queueName });
150
+ const EventHandler = (queueName, isCloudEvent = true) => SetMetadata(EVENT_HANDLER, { name: queueName, isCloudEvent });
148
151
 
149
- export { CloudEventHandler, CloudEventReceiverModule };
152
+ export { CloudEventReceiverModule, EventHandler };
package/package.json CHANGED
@@ -1,9 +1,9 @@
1
1
  {
2
2
  "name": "@yourrentals/cloudevent-receiver-nestjs",
3
- "version": "0.4.2",
3
+ "version": "0.5.0",
4
4
  "dependencies": {
5
- "@yourrentals/cloudevent-receiver-core": "0.4.2",
6
- "@yourrentals/util-receiver-error": "0.4.2"
5
+ "@yourrentals/cloudevent-receiver-core": "0.5.0",
6
+ "@yourrentals/util-receiver-error": "0.5.0"
7
7
  },
8
8
  "peerDependencies": {
9
9
  "@nestjs/common": "^6.0.0 || ^7.0.0 || ^8.0.0 || ^9.0.0 || ^10.0.0",
package/src/index.d.ts CHANGED
@@ -1,4 +1,3 @@
1
1
  export * from './lib/cloud-event-receiver.module';
2
2
  export * from './lib/cloud-event-receiver.decorators';
3
3
  export * from '@yourrentals/cloudevent-receiver-core';
4
- export * from '@yourrentals/util-receiver-error';
@@ -1,2 +1,2 @@
1
- export declare const CLOUD_EVENT_HANDLER: unique symbol;
1
+ export declare const EVENT_HANDLER: unique symbol;
2
2
  export declare const CLOUD_EVENT_RECEIVER_OPTIONS: unique symbol;
@@ -1,7 +1,8 @@
1
+ import { RawBodyRequest } from '@nestjs/common';
1
2
  import { CloudEventReceiverService } from './cloud-event-receiver.service';
2
- export declare const createController: (pathPrefix?: string) => {
3
+ export declare const createController: (pathPrefix?: string, routeDecorator?: MethodDecorator) => {
3
4
  new (service: CloudEventReceiverService): {
4
5
  readonly service: CloudEventReceiverService;
5
- receiveMessage(request: any): Promise<void>;
6
+ receiveMessage(request: RawBodyRequest<any>): Promise<void>;
6
7
  };
7
8
  };
@@ -1 +1 @@
1
- export declare const CloudEventHandler: (queueName: string) => MethodDecorator;
1
+ export declare const EventHandler: (queueName: string, isCloudEvent?: boolean) => MethodDecorator;
@@ -1,5 +1,15 @@
1
1
  import type { CloudEventReceiverOptions } from '@yourrentals/cloudevent-receiver-core';
2
2
  export type CloudEventReceiverModuleOptions = {
3
+ /**
4
+ * Path prefix. Used to prefix the route path
5
+ */
3
6
  pathPrefix?: string;
4
- verifiers?: CloudEventReceiverOptions['verifiers'];
7
+ /**
8
+ * Verifier plugins. Used to verify the authenticity of the incoming CloudEvent
9
+ */
10
+ verifierPlugins?: CloudEventReceiverOptions['verifierPlugins'];
11
+ /**
12
+ * Route Decorator. Used to annotate the route controller method, usually for bypassing authentication
13
+ */
14
+ routeDecorator?: MethodDecorator;
5
15
  };