@open-rlb/nestjs-amqp 2.0.4 → 2.0.5
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 +4 -2
- package/amqp-lib/config/rabbitmq.config.d.ts +6 -0
- package/modules/acl/const.d.ts +0 -1
- package/modules/acl/const.js +0 -1
- package/modules/acl/const.js.map +1 -1
- package/modules/acl/models.d.ts +5 -7
- package/modules/acl/repository/acl-action.repository.d.ts +1 -5
- package/modules/acl/repository/acl-action.repository.js.map +1 -1
- package/modules/acl/repository/acl-role.repository.d.ts +1 -5
- package/modules/acl/repository/acl-role.repository.js.map +1 -1
- package/modules/acl/services/acl-management.service.d.ts +2 -2
- package/modules/acl/services/acl-management.service.js +17 -20
- package/modules/acl/services/acl-management.service.js.map +1 -1
- package/modules/acl/services/acl.service.d.ts +1 -2
- package/modules/acl/services/acl.service.js +5 -21
- package/modules/acl/services/acl.service.js.map +1 -1
- package/modules/broker/broker.module.d.ts +2 -4
- package/modules/broker/broker.module.js +23 -5
- package/modules/broker/broker.module.js.map +1 -1
- package/modules/broker/config/route-discovery.config.d.ts +2 -0
- package/modules/broker/const.d.ts +1 -0
- package/modules/broker/const.js +2 -1
- package/modules/broker/const.js.map +1 -1
- package/modules/broker/services/broker.service.js +1 -1
- package/modules/broker/services/broker.service.js.map +1 -1
- package/modules/broker/services/route-discovery-publisher.service.js +7 -5
- package/modules/broker/services/route-discovery-publisher.service.js.map +1 -1
- package/modules/gateway-admin/config/gateway-admin.config.d.ts +4 -0
- package/modules/gateway-admin/const.d.ts +1 -1
- package/modules/gateway-admin/const.js +1 -1
- package/modules/gateway-admin/const.js.map +1 -1
- package/modules/gateway-admin/gateway-admin.module.d.ts +7 -1
- package/modules/gateway-admin/gateway-admin.module.js +13 -0
- package/modules/gateway-admin/gateway-admin.module.js.map +1 -1
- package/modules/gateway-admin/repository/auth-provider.repository.d.ts +3 -4
- package/modules/gateway-admin/repository/auth-provider.repository.js.map +1 -1
- package/modules/gateway-admin/services/gateway-auth.service.d.ts +3 -4
- package/modules/gateway-admin/services/gateway-auth.service.js +15 -23
- package/modules/gateway-admin/services/gateway-auth.service.js.map +1 -1
- package/modules/gateway-admin/services/gateway-metrics.service.d.ts +3 -0
- package/modules/gateway-admin/services/gateway-metrics.service.js +9 -0
- package/modules/gateway-admin/services/gateway-metrics.service.js.map +1 -1
- package/modules/gateway-admin/services/route-sync.service.d.ts +3 -1
- package/modules/gateway-admin/services/route-sync.service.js +14 -8
- package/modules/gateway-admin/services/route-sync.service.js.map +1 -1
- package/modules/proxy/services/http-handler.service.d.ts +3 -0
- package/modules/proxy/services/http-handler.service.js +27 -3
- package/modules/proxy/services/http-handler.service.js.map +1 -1
- package/package.json +5 -1
- package/schematics/nest-add/files/acl/src/cache/in-memory-acl-store.ts +54 -0
- package/schematics/nest-add/files/acl/src/modules/database/repository/acl.repository.ts +74 -0
- package/schematics/nest-add/files/db-core/src/modules/database/repository/in-memory-collection.ts +122 -0
- package/schematics/nest-add/files/gateway-admin/src/modules/database/repository/gateway.repository.ts +151 -0
- package/schematics/nest-add/files/gateway-admin/src/modules/database/repository/route-sync.repository.ts +15 -0
- package/schematics/nest-add/files/skills/rlb-amqp/SKILL.md +30 -5
- package/schematics/nest-add/files/skills/rlb-amqp/references/config-schema.md +182 -93
- package/schematics/nest-add/files/skills/rlb-amqp/references/gotchas.md +120 -79
- package/schematics/nest-add/files/skills/rlb-amqp-acl/SKILL.md +185 -0
- package/schematics/nest-add/files/skills/rlb-amqp-add-action/SKILL.md +49 -2
- package/schematics/nest-add/files/skills/rlb-amqp-add-route/SKILL.md +82 -19
- package/schematics/nest-add/files/skills/rlb-amqp-add-ws-event/SKILL.md +40 -18
- package/schematics/nest-add/files/skills/rlb-amqp-gateway-admin/SKILL.md +233 -0
- package/schematics/nest-add/files/skills/rlb-amqp-scaffold/SKILL.md +172 -42
- package/schematics/nest-add/index.js +612 -142
- package/schematics/nest-add/index.js.map +1 -1
- package/schematics/nest-add/index.ts +673 -241
- package/schematics/nest-add/init.schema.d.ts +10 -1
- package/schematics/nest-add/init.schema.ts +29 -3
- package/schematics/nest-add/schema.json +37 -8
|
@@ -1,8 +1,19 @@
|
|
|
1
1
|
# config.yaml — full schema
|
|
2
2
|
|
|
3
3
|
Five top-level sections: `app`, `broker`, `topics`, `auth-providers`, `gateway`.
|
|
4
|
-
Loaded by `config/config.loader.ts`. `app`/`broker`/`topics`
|
|
5
|
-
`auth-providers` + `gateway`
|
|
4
|
+
Loaded by `config/config.loader.ts`. `app`/`broker`/`topics` → `BrokerModule.forRoot(broker, topics, app?)`;
|
|
5
|
+
`auth-providers` + `gateway` → `ProxyModule.forRootAsync` (`authOptions` / `gatewayOptions`).
|
|
6
|
+
Gateway-admin repos + the consumer-side `routeDiscovery` are wired in NEST code via
|
|
7
|
+
`GatewayAdminModule.forRoot/forRootAsync` — not YAML.
|
|
8
|
+
|
|
9
|
+
Authoritative sources: `docs/broker.md`, `docs/gateway.md`, `docs/gateway-admin.md`,
|
|
10
|
+
`docs/acl.md`, and the annotated reference YAMLs under `sample/config-sample/`
|
|
11
|
+
(`broker.yaml`, `gateway.yaml`, `acl.yaml`, `gateway-admin.yaml`).
|
|
12
|
+
|
|
13
|
+
> **Decorator-bound vs configurable.** Topic NAMES `rlb-acl` / `rlb-gateway-admin` /
|
|
14
|
+
> `rlb-gateway-control` and ALL action strings are fixed in code — write them exactly.
|
|
15
|
+
> `exchange` / `queue` / `routingKey` and the route-discovery `exchange` / `queue` ARE
|
|
16
|
+
> configurable.
|
|
6
17
|
|
|
7
18
|
---
|
|
8
19
|
|
|
@@ -24,47 +35,62 @@ to `{ message, name }`; in `development` the full detail/stack is included.
|
|
|
24
35
|
|
|
25
36
|
```yaml
|
|
26
37
|
broker:
|
|
27
|
-
name: rabbitmq
|
|
28
|
-
uri: "amqp://user:pass@host:5672/vhost"
|
|
29
|
-
prefetchCount: 10
|
|
30
|
-
defaultRpcTimeout: 10000 # ms
|
|
31
|
-
defaultSubscribeErrorBehavior: ack # ack |
|
|
38
|
+
name: rabbitmq # cosmetic label (optional)
|
|
39
|
+
uri: "amqp://user:pass@host:5672/vhost" # string | string[] (failover); vhost after last "/"
|
|
40
|
+
prefetchCount: 10 # default channel prefetch
|
|
41
|
+
defaultRpcTimeout: 10000 # ms (call arg → topic → this → 10000)
|
|
42
|
+
defaultSubscribeErrorBehavior: ack # ack | nack | requeue (lib default REQUEUE)
|
|
32
43
|
defaultPublishErrorBehavior: reject
|
|
33
44
|
|
|
45
|
+
routeDiscovery: # PUBLISHER-side route auto-discovery (microservice only)
|
|
46
|
+
serviceName: demo-ms # required to publish; fills connection_name if unset
|
|
47
|
+
publishOnBoot: true # default true — announce manifest on bootstrap
|
|
48
|
+
exchange: rlb-route-discovery # default; MUST match the gateway consumer
|
|
49
|
+
queue: rlb-route-sync # default; MUST match the gateway consumer
|
|
50
|
+
|
|
34
51
|
connectionManagerOptions: # amqp-connection-manager options
|
|
35
52
|
heartbeatIntervalInSeconds: 60
|
|
36
53
|
reconnectTimeInSeconds: 60
|
|
37
54
|
connectionOptions:
|
|
38
55
|
clientProperties:
|
|
39
|
-
connection_name: my-service
|
|
56
|
+
connection_name: my-service-1 # DISTINCT per instance for broadcast + WebSocket
|
|
40
57
|
credentials:
|
|
41
58
|
mechanism: PLAIN # PLAIN | EXTERNAL | AMQPLAIN (case-insensitive)
|
|
42
59
|
username: guest
|
|
43
60
|
password: guest
|
|
44
61
|
|
|
62
|
+
connectionInitOptions: # block on a healthy connection at boot?
|
|
63
|
+
wait: true # default true
|
|
64
|
+
timeout: 5000 # default 5000 ms
|
|
65
|
+
reject: true # default true → throw on timeout
|
|
66
|
+
|
|
45
67
|
exchanges: # RabbitMQExchangeConfig[]
|
|
46
|
-
- name:
|
|
68
|
+
- name: rlb
|
|
47
69
|
type: direct # direct | topic | fanout | headers
|
|
48
70
|
createExchangeIfNotExists: true # false → checkExchange (must pre-exist)
|
|
49
71
|
options: { durable: true, autoDelete: false, internal: false }
|
|
50
72
|
|
|
51
73
|
queues: # RabbitMQQueueConfig[]
|
|
52
|
-
- name:
|
|
53
|
-
exchange:
|
|
54
|
-
routingKey:
|
|
74
|
+
- name: rlb-acl
|
|
75
|
+
exchange: rlb
|
|
76
|
+
routingKey: rlb-acl # string | string[]; REQUIRED if exchange type == topic
|
|
55
77
|
createQueueIfNotExists: true
|
|
56
78
|
options: { durable: true, exclusive: false, autoDelete: false }
|
|
57
|
-
consumerTag: my-tag # optional,
|
|
79
|
+
consumerTag: my-tag # optional, unique per channel
|
|
58
80
|
|
|
59
81
|
replyQueues: # map exchange → reply queue (RPC responses)
|
|
60
|
-
|
|
82
|
+
rlb: rlb-reply # omit → RabbitMQ direct-reply-to is used
|
|
61
83
|
```
|
|
62
84
|
|
|
63
85
|
Notes:
|
|
64
|
-
- `exchanges[]`
|
|
65
|
-
- `
|
|
66
|
-
|
|
67
|
-
|
|
86
|
+
- `exchanges[]` / `queues[]` are asserted/checked once at boot; `replyQueues` values auto-consumed.
|
|
87
|
+
- `routeDiscovery.serviceName` doubles as `connection_name` when no explicit `clientProperties.connection_name`
|
|
88
|
+
is set (explicit always wins). The **gateway** (consumer) does NOT use `broker.routeDiscovery` —
|
|
89
|
+
it sets its side via `GatewayAdminModule` `routeDiscovery { exchange, queue }`; both sides must match.
|
|
90
|
+
- Do NOT declare a `broadcast` topic's per-instance queue here — the broker asserts
|
|
91
|
+
`${topic}-${connection_name}` for you at subscribe time.
|
|
92
|
+
- Other optional fields: `defaultAlternateExchange` (divert unroutable), `onUnroutableMessage`
|
|
93
|
+
(callback, needs `mandatory: true` publishes), per-channel `channels` map.
|
|
68
94
|
|
|
69
95
|
---
|
|
70
96
|
|
|
@@ -74,60 +100,70 @@ A topic maps a logical name to an AMQP path. `mode` decides the semantics.
|
|
|
74
100
|
|
|
75
101
|
```yaml
|
|
76
102
|
topics:
|
|
77
|
-
- name:
|
|
103
|
+
- name: rlb-acl # logical name (must match @BrokerAction / requestData / gateway)
|
|
78
104
|
mode: rpc # rpc | handle | broadcast | event
|
|
79
|
-
queue:
|
|
80
|
-
exchange:
|
|
81
|
-
routingKey:
|
|
105
|
+
queue: rlb-acl # for rpc/handle: must exist in broker.queues[]
|
|
106
|
+
exchange: rlb # exchange name
|
|
107
|
+
routingKey: rlb-acl # broadcast / topic exchanges
|
|
108
|
+
errorBehavior: ack # per-topic override of defaultSubscribeErrorBehavior (default REQUEUE)
|
|
109
|
+
mandatory: false # publish with AMQP `mandatory` (unroutable → returned)
|
|
110
|
+
persistent: false # publish delivery-mode 2 (survives restart if queue durable)
|
|
82
111
|
toObservable: false # handle only: route to BrokerService.events$ instead of a handler
|
|
83
112
|
```
|
|
84
113
|
|
|
85
|
-
| mode | required fields | notes
|
|
86
|
-
| ----------- | ------------------------------------------------ |
|
|
87
|
-
| `rpc` | `name`, `queue` (or `exchange
|
|
88
|
-
| `handle` | `name`, `queue` |
|
|
89
|
-
| `broadcast` | `name`, `exchange`, `routingKey` |
|
|
90
|
-
| `event` | `name`, `
|
|
114
|
+
| mode | required fields | notes |
|
|
115
|
+
| ----------- | ------------------------------------------------ | ------------------------------------------------------------------ |
|
|
116
|
+
| `rpc` | `name`, `queue` (or `exchange`) | request/response + timeout; also the mode `@BrokerAction` uses |
|
|
117
|
+
| `handle` | `name`, `queue` | plain consumer, no reply (`registerHandler` / `toObservable`) |
|
|
118
|
+
| `broadcast` | `name`, `exchange`, `routingKey` | per-instance queue `${topic}-${connection_name}`; distinct name |
|
|
119
|
+
| `event` | `name`, `exchange`+`routingKey` (or `queue`) | fire-and-forget publish; no consumer asserted for the topic |
|
|
91
120
|
|
|
92
121
|
> A single `@BrokerAction` topic registers ONE consumer; multiple actions on the same
|
|
93
|
-
> topic share it and are dispatched by `action`.
|
|
122
|
+
> topic share it and are dispatched by `action`. The gateway control topic
|
|
123
|
+
> (`rlb-gateway-control`) is `broadcast` so every instance reloads.
|
|
94
124
|
|
|
95
125
|
---
|
|
96
126
|
|
|
97
127
|
## auth-providers (HandlerAuthConfig[])
|
|
98
128
|
|
|
129
|
+
Top-level (NOT under `gateway`). Each provider VALIDATES a token and MAPS its claims into
|
|
130
|
+
forwarded `X-GTW-AUTH-*` headers. Referenced by `paths[].auth` / `events[].auth` by `name`.
|
|
131
|
+
|
|
99
132
|
```yaml
|
|
100
133
|
auth-providers:
|
|
101
134
|
- name: gateway-jwks
|
|
102
135
|
type: jwks # jwt | jwks | basic | str-compare | none
|
|
103
|
-
|
|
136
|
+
headerPrefix: "X-GTW-AUTH-" # prefix for mapped claim headers (and <prefix>USERID)
|
|
137
|
+
uidClaim: sub # claim → <prefix>USERID; REQUIRED for role checks
|
|
138
|
+
jwtMap: # 'source:dest' pairs → <prefix><DEST>; WITHOUT it NO claims forwarded
|
|
139
|
+
- sub:userId # → X-GTW-AUTH-USERID
|
|
140
|
+
- email:email # → X-GTW-AUTH-EMAIL
|
|
141
|
+
- preferred_username:username # → X-GTW-AUTH-USERNAME
|
|
142
|
+
- roles:roles # → X-GTW-AUTH-ROLES
|
|
143
|
+
algorithms: [RS256] # REQUIRED for jwt/jwks; jwks allows only RS*/ES*/PS*
|
|
144
|
+
issuer: https://issuer/realms/x # expected `iss`
|
|
104
145
|
jwksUri: https://issuer/certs # jwks only
|
|
105
|
-
secret: s3cr3t # jwt / str-compare
|
|
106
|
-
audience: my-aud # jwt only
|
|
107
|
-
|
|
146
|
+
secret: s3cr3t # jwt (HS secret) / str-compare (expected token string)
|
|
147
|
+
audience: my-aud # jwt only (optional)
|
|
148
|
+
clientId: u # basic only (username)
|
|
149
|
+
clientSecret: p # basic only (password)
|
|
108
150
|
httpsAllowUnauthorized: false # true ONLY for self-signed dev issuers
|
|
109
|
-
clientId: u # basic only
|
|
110
|
-
clientSecret: p # basic only
|
|
111
|
-
jwtMap: [sub:userId, roles:roles]# tokenClaim:destClaim (dest is header-prefixed + uppercased)
|
|
112
|
-
headerPrefix: X-GTW-AUTH- # prefix of headers propagated to microservices
|
|
113
|
-
uidClaim: USERID # dest used as user id for ACL
|
|
114
|
-
usernameClaim: USERNAME
|
|
115
|
-
# aclTopic / aclAction are DEPRECATED & unused: the gateway role check runs in-process
|
|
116
|
-
# (IAclRoleService.canUserDoGtw) — no RPC topic/action needed.
|
|
117
151
|
```
|
|
118
152
|
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
153
|
+
Type behaviour:
|
|
154
|
+
- **`jwt` / `jwks`** — verify `Authorization: Bearer <token>`, then map via `jwtMap`.
|
|
155
|
+
- **`basic`** — verify `Authorization: Basic` vs `clientId`/`clientSecret`; maps username to
|
|
156
|
+
`<prefix>USERNAME`/`<prefix>USERID`. **No `clientSecret` → passes through** (open by design).
|
|
157
|
+
- **`str-compare`** — compare raw `Authorization` to `secret`; maps to `<prefix>TOKEN`.
|
|
158
|
+
**No `secret` → passes through.**
|
|
122
159
|
|
|
123
|
-
|
|
124
|
-
`
|
|
160
|
+
Hardening: `algorithms` REQUIRED for `jwt`/`jwks` (omit → denied; algorithm-confusion guard).
|
|
161
|
+
Define `jwtMap` or no identity is forwarded (token still `success:true` — fail-safe, not leak).
|
|
162
|
+
`usernameClaim` is deprecated; `aclTopic`/`aclAction` are removed (gateway role check is in-process
|
|
163
|
+
via `IAclRoleService.canUserDoGtw`).
|
|
125
164
|
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
`clientSecret` PASS THROUGH (request treated as authenticated — provider effectively open).
|
|
129
|
-
Define `jwtMap` to forward identity headers: without it NO claims are forwarded (the token is
|
|
130
|
-
still accepted, `success:true`) — fail-safe instead of leaking the whole payload.
|
|
165
|
+
> DB-stored auth-providers (name-keyed `gw-auth-*` upserts) layer on top of this static list —
|
|
166
|
+
> see `docs/gateway-admin.md`.
|
|
131
167
|
|
|
132
168
|
---
|
|
133
169
|
|
|
@@ -136,80 +172,133 @@ still accepted, `success:true`) — fail-safe instead of leaking the whole paylo
|
|
|
136
172
|
```yaml
|
|
137
173
|
gateway:
|
|
138
174
|
mode: gateway
|
|
139
|
-
headerPrefix: X-
|
|
175
|
+
headerPrefix: "X-FWD-" # prefix for FORWARDED request headers (forwardHeaders);
|
|
176
|
+
# separate from a provider's headerPrefix (auth claims)
|
|
177
|
+
|
|
178
|
+
reloadTopic: rlb-gateway-control # broadcast control topic; action 'gw-reload' rebuilds routes
|
|
179
|
+
metrics: # per-call broker sink (omit to disable)
|
|
180
|
+
topic: rlb-gateway-admin
|
|
181
|
+
action: gw-metrics-track
|
|
182
|
+
|
|
183
|
+
loadConfig: # pull DB-stored routes/events, merged with YAML, on (re)load
|
|
184
|
+
paths: { topic: rlb-gateway-admin, action: gw-path-export } # gateway-admin ships this handler
|
|
185
|
+
# events: { topic: <topic>, action: <your-export-action> } # optional; NO built-in handler — provide your own
|
|
140
186
|
|
|
141
187
|
ws: # WebSocketGatewayOptions — connection-level only
|
|
142
|
-
maxConnections:
|
|
188
|
+
maxConnections: 1000
|
|
143
189
|
maxSubscriptionsPerClient: 50
|
|
144
|
-
heartbeatIntervalMs: 30000
|
|
145
|
-
|
|
146
|
-
|
|
190
|
+
heartbeatIntervalMs: 30000 # default 30000; also drops dead/expired-token sockets
|
|
191
|
+
maxMessageBytes: 16384 # default 16384; oversized client frames dropped
|
|
192
|
+
allowedOrigins: [https://app.example.com] # omit → all Origins accepted (logged)
|
|
147
193
|
# auth/roles/scope are declared PER-EVENT on events[], not here
|
|
148
194
|
|
|
149
|
-
loadConfig: # optional remote load via RPC at boot
|
|
150
|
-
paths: { topic: gtw.config, action: get-paths }
|
|
151
|
-
events: { topic: gtw.config, action: get-events }
|
|
152
|
-
|
|
153
195
|
paths: [ ... ] # PathDefinition[] (HTTP routes) — see below
|
|
154
196
|
events: [ ... ] # WebSocketEvent[] (WS / webhook) — see below
|
|
155
197
|
```
|
|
156
198
|
|
|
199
|
+
The reload control action is the literal string **`gw-reload`** (`GW_RELOAD_ACTION`); the
|
|
200
|
+
control-topic subscriber ignores every other message. Concurrent `reload()`s are coalesced.
|
|
201
|
+
|
|
157
202
|
### gateway.paths[] (PathDefinition — HTTP routes)
|
|
158
203
|
|
|
159
204
|
```yaml
|
|
160
|
-
- name:
|
|
161
|
-
method:
|
|
162
|
-
path: /
|
|
163
|
-
dataSource: body
|
|
164
|
-
topic:
|
|
165
|
-
action:
|
|
166
|
-
mode: rpc
|
|
167
|
-
timeout:
|
|
168
|
-
auth: gateway-jwks
|
|
169
|
-
allowAnonymous: false
|
|
170
|
-
roles: [
|
|
171
|
-
successStatusCode:
|
|
172
|
-
binary:
|
|
173
|
-
redirect: 302
|
|
174
|
-
parseRaw: false
|
|
205
|
+
- name: report-download # logical name (logs + metrics)
|
|
206
|
+
method: GET # GET | POST | PUT | DELETE | PATCH
|
|
207
|
+
path: /reports/:id # Express route, :params merged into payload (params win)
|
|
208
|
+
dataSource: query-body # body | query | params | body-query | query-body
|
|
209
|
+
topic: rlb-gateway-admin
|
|
210
|
+
action: gw-metrics-get
|
|
211
|
+
mode: rpc # rpc | event
|
|
212
|
+
timeout: 15000 # rpc only (ms)
|
|
213
|
+
auth: gateway-jwks # auth-provider name
|
|
214
|
+
allowAnonymous: false # true → skip the auth/role gate entirely
|
|
215
|
+
roles: [user, admin] # caller must hold AT LEAST ONE; requires auth + IAclRoleService
|
|
216
|
+
successStatusCode: 200 # default 200 rpc / 202 event / 204 empty rpc reply
|
|
217
|
+
binary: true # treat a raw (non-JSON) reply as base64 → binary body
|
|
218
|
+
redirect: 302 # rpc only → redirect with this status, using the reply as Location
|
|
219
|
+
parseRaw: false # true → forward raw body as $raw (needs rawBody:true at bootstrap)
|
|
175
220
|
headers: { Cache-Control: no-store } # static response headers
|
|
176
|
-
forwardHeaders: {
|
|
221
|
+
forwardHeaders: { X-Trace-Id: X-Request-Id } # request header → forwarded (dest prefixed by headerPrefix)
|
|
177
222
|
```
|
|
178
223
|
|
|
179
|
-
dataSource payload composition:
|
|
224
|
+
dataSource payload composition (`req.params` always merged in, re-applied last so they win):
|
|
180
225
|
|
|
181
226
|
| value | payload |
|
|
182
227
|
| ------------ | -------------------------------- |
|
|
183
228
|
| `body` | `{...params, ...body}` |
|
|
184
229
|
| `query` | `{...params, ...query}` |
|
|
185
|
-
| `params` | `params`
|
|
230
|
+
| `params` | `{...params}` |
|
|
186
231
|
| `body-query` | `{...params, ...query, ...body}` |
|
|
187
232
|
| `query-body` | `{...params, ...body, ...query}` |
|
|
188
233
|
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
|
|
234
|
+
Uploads → `$files` (buffers as binary strings), raw → `$raw`.
|
|
235
|
+
|
|
236
|
+
**rpc status:** defined reply (incl. falsy `false`/`0`/`""`) → `200` + body; `null`/`undefined`
|
|
237
|
+
→ `204`. Error `name` → status: BadRequestError/InvalidParamsErrror→400, UnauthorizedError→401,
|
|
238
|
+
ForbiddenError→403, NotFoundError→404, ConflictError→409, else→500.
|
|
239
|
+
**event status:** successful publish → `successStatusCode || 202`; publish failure → `503`.
|
|
240
|
+
|
|
241
|
+
Auth gate (per request): `allowAnonymous:true` → gate skipped; `auth` no `roles` → authn only
|
|
242
|
+
(401 if invalid); `auth` + `roles` → authn then in-process `canUserDoGtw(roles, userId)` (403 if
|
|
243
|
+
no role). `roles` without `auth` fails closed (every request 403).
|
|
192
244
|
|
|
193
245
|
### gateway.events[] (WebSocketEvent — WS / webhook)
|
|
194
246
|
|
|
195
247
|
```yaml
|
|
196
|
-
- name:
|
|
197
|
-
type: ws # ws | http (webhook)
|
|
198
|
-
exchange:
|
|
199
|
-
routingKey:
|
|
248
|
+
- name: chat # clients subscribe to "chat"; messages arrive as onChat
|
|
249
|
+
type: ws # ws | mqtt (reserved) | http (webhook)
|
|
250
|
+
exchange: rlb # broker source bound to a per-instance exclusive ephemeral queue
|
|
251
|
+
routingKey: chat.messages
|
|
200
252
|
auth: gateway-jwks # provider that verifies the token + maps claims FOR THIS event (at subscribe)
|
|
201
253
|
requireAuth: true # default true when `auth` is set; false → auth optional (anon allowed)
|
|
202
|
-
roles: [
|
|
203
|
-
scopeClaim:
|
|
204
|
-
payloadKey: userId
|
|
254
|
+
roles: [user] # ACL check via IAclRoleService (needs auth)
|
|
255
|
+
scopeClaim: userId # per-user isolation: the mapped claim value...
|
|
256
|
+
payloadKey: userId # ...must equal payload[payloadKey]; without payloadKey → denies everything
|
|
205
257
|
# type: http only:
|
|
206
258
|
url: https://hooks.example.com/orders
|
|
207
259
|
method: POST
|
|
208
|
-
timeout:
|
|
260
|
+
timeout: 5000
|
|
209
261
|
headers: { Authorization: "Bearer ..." }
|
|
210
262
|
```
|
|
211
263
|
|
|
212
|
-
WS client connects with the JWT in the subprotocol: `new WebSocket(url, [token])
|
|
213
|
-
|
|
214
|
-
|
|
215
|
-
|
|
264
|
+
WS client connects with the JWT in the subprotocol: `new WebSocket(url, [token])` (browsers
|
|
265
|
+
can't set handshake headers). A bare value, or `['bearer', '<token>']` / `['jwt', '<token>']`,
|
|
266
|
+
is accepted. Token verified per-event at subscribe (memoized per provider per connection);
|
|
267
|
+
session bounded by the JWT `exp` (closed `1008` on expiry). Client protocol:
|
|
268
|
+
`{action:'subscribe'|'unsubscribe', topic, select?}`; inbound `{ topic:'on<Name>', data }`;
|
|
269
|
+
errors `{ topic:'onError', data:{event,error} }` (`unauthorized`, `forbidden`,
|
|
270
|
+
`subscription_limit`, `unknown_event`). `select` is a client filter, intersected with — never
|
|
271
|
+
bypassing — the server `scopeClaim` isolation. Each instance binds its own exclusive
|
|
272
|
+
auto-delete queue, so every instance needs a distinct `connection_name`.
|
|
273
|
+
|
|
274
|
+
---
|
|
275
|
+
|
|
276
|
+
## gateway-admin / ACL HTTP surface (decorator-bound topics + actions)
|
|
277
|
+
|
|
278
|
+
These are exposed as ordinary `gateway.paths[]` entries forwarding to the fixed topics/actions
|
|
279
|
+
below. **Name-keyed** resources have NO POST and no id — `PUT` upserts by `name`.
|
|
280
|
+
|
|
281
|
+
ACL (topic `rlb-acl`):
|
|
282
|
+
|
|
283
|
+
| Method | Path | Action |
|
|
284
|
+
| --- | --- | --- |
|
|
285
|
+
| GET | `/acl/check` | `acl-can-user-do-gtw` (resource-agnostic; `200` true/false) |
|
|
286
|
+
| GET | `/acl/check-resource` | `acl-can-user-do` (resource-scoped; `200` true/false) |
|
|
287
|
+
| GET | `/acl/resources` | `acl-list-resources-by-user` (auth, reads `X-GTW-AUTH-USERID`) |
|
|
288
|
+
| POST | `/acl/grants` | `acl-grant` (`{userId, roles, resourceId?, companyId?}`) |
|
|
289
|
+
| DELETE | `/acl/grants` | `acl-revoke` (same shape; `roles` REQUIRED) |
|
|
290
|
+
| GET / PUT / DELETE | `/acl/actions[/get?name=]` | `acl-action-list`/`-update`/`-delete`/`-get` |
|
|
291
|
+
| GET / PUT / DELETE | `/acl/roles[/get?name=]` | `acl-role-list`/`-update`/`-delete`/`-get` |
|
|
292
|
+
|
|
293
|
+
Gateway-admin (topic `rlb-gateway-admin`, except reload):
|
|
294
|
+
|
|
295
|
+
| Method | Path | Action |
|
|
296
|
+
| --- | --- | --- |
|
|
297
|
+
| GET | `/health` | `gw-health` → `{ status: 'ok' }` |
|
|
298
|
+
| POST / GET / PUT / DELETE | `/admin/paths[/export\|/get]` | `gw-path-create`/`-list`/`-export`/`-update`/`-get`/`-delete` (**id-keyed, POST creates**) |
|
|
299
|
+
| GET / PUT / DELETE | `/admin/auth[/get?name=]` | `gw-auth-list`/`-update`/`-delete`/`-get` (**name-keyed PUT-upsert**) |
|
|
300
|
+
| GET | `/admin/metrics[/series\|/points]` | `gw-metrics-get`/`-series`/`-points` |
|
|
301
|
+
| POST | `/admin/metrics/track` | `gw-metrics-track` (`mode: event`) |
|
|
302
|
+
| POST | `/admin/reload` | `gw-reload` on `rlb-gateway-control` (`mode: event`) |
|
|
303
|
+
|
|
304
|
+
> Removed: `acl-list-by-user`, `acl-verify-access`, `gw-auth-create`, all id-based ACL CRUD.
|