@attraccess/plugin-rabbitmq 0.1.0-alpha.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/dist/index.js ADDED
@@ -0,0 +1,1108 @@
1
+ var __defProp = Object.defineProperty;
2
+ var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
3
+ var __getOwnPropNames = Object.getOwnPropertyNames;
4
+ var __hasOwnProp = Object.prototype.hasOwnProperty;
5
+ var __defNormalProp = (obj, key, value) => key in obj ? __defProp(obj, key, { enumerable: true, configurable: true, writable: true, value }) : obj[key] = value;
6
+ var __export = (target, all) => {
7
+ for (var name in all)
8
+ __defProp(target, name, { get: all[name], enumerable: true });
9
+ };
10
+ var __copyProps = (to, from, except, desc) => {
11
+ if (from && typeof from === "object" || typeof from === "function") {
12
+ for (let key of __getOwnPropNames(from))
13
+ if (!__hasOwnProp.call(to, key) && key !== except)
14
+ __defProp(to, key, { get: () => from[key], enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable });
15
+ }
16
+ return to;
17
+ };
18
+ var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod);
19
+ var __decorateClass = (decorators, target, key, kind) => {
20
+ var result = kind > 1 ? void 0 : kind ? __getOwnPropDesc(target, key) : target;
21
+ for (var i = decorators.length - 1, decorator; i >= 0; i--)
22
+ if (decorator = decorators[i])
23
+ result = (kind ? decorator(target, key, result) : decorator(result)) || result;
24
+ if (kind && result) __defProp(target, key, result);
25
+ return result;
26
+ };
27
+ var __decorateParam = (index, decorator) => (target, key) => decorator(target, key, index);
28
+ var __publicField = (obj, key, value) => __defNormalProp(obj, typeof key !== "symbol" ? key + "" : key, value);
29
+
30
+ // backend/plugin.ts
31
+ var plugin_exports = {};
32
+ __export(plugin_exports, {
33
+ default: () => plugin_default
34
+ });
35
+ module.exports = __toCommonJS(plugin_exports);
36
+ var import_plugins_backend_sdk3 = require("@attraccess/plugins-backend-sdk");
37
+ var import_common7 = require("@nestjs/common");
38
+
39
+ // backend/rabbitmq-detection.controller.ts
40
+ var import_plugins_backend_sdk = require("@attraccess/plugins-backend-sdk");
41
+ var import_common2 = require("@nestjs/common");
42
+
43
+ // backend/rabbitmq-detection.service.ts
44
+ var import_common = require("@nestjs/common");
45
+ var import_node_crypto2 = require("node:crypto");
46
+
47
+ // backend/rabbitmq-management-transport.ts
48
+ var import_node_crypto = require("node:crypto");
49
+ var import_node_https = require("node:https");
50
+ var import_node_net = require("node:net");
51
+ var TrustConfigurationError = class extends Error {
52
+ };
53
+ function managementApiBase(config) {
54
+ const host = (0, import_node_net.isIP)(config.host) === 6 ? `[${config.host}]` : config.host;
55
+ const port = config.managementPort ?? (config.useTls ? 15671 : 15672);
56
+ return `${config.useTls ? "https" : "http"}://${host}:${port}`;
57
+ }
58
+ function trustOptions(config) {
59
+ if (config.tlsInsecure) {
60
+ throw new TrustConfigurationError(
61
+ "RabbitMQ management requires certificate verification. Disable tlsInsecure and configure a trusted CA certificate."
62
+ );
63
+ }
64
+ const servername = config.tlsServername === "" ? void 0 : config.tlsServername;
65
+ if (servername != null && (typeof servername !== "string" || servername.length > 253 || !servername.split(".").every((label) => /^[a-z\d](?:[a-z\d-]{0,61}[a-z\d])?$/i.test(label)) || (0, import_node_net.isIP)(servername))) {
66
+ throw new TrustConfigurationError(
67
+ "TLS server name must be a DNS hostname matching the management certificate, without a scheme or port."
68
+ );
69
+ }
70
+ const caCert = config.caCert === "" ? void 0 : config.caCert;
71
+ if (caCert != null) {
72
+ try {
73
+ const pem = caCert;
74
+ const certificates = typeof pem === "string" ? pem.match(/-----BEGIN CERTIFICATE-----[\s\S]*?-----END CERTIFICATE-----/g) : null;
75
+ if (!certificates?.length || pem.replace(/-----BEGIN CERTIFICATE-----[\s\S]*?-----END CERTIFICATE-----/g, "").trim()) {
76
+ throw new Error();
77
+ }
78
+ for (const certificate of certificates) new import_node_crypto.X509Certificate(certificate);
79
+ } catch {
80
+ throw new TrustConfigurationError(
81
+ "Invalid CA certificate. Configure a PEM certificate or PEM certificate bundle."
82
+ );
83
+ }
84
+ }
85
+ return {
86
+ rejectUnauthorized: true,
87
+ ...caCert != null ? { ca: caCert } : {},
88
+ ...servername != null ? { servername } : {}
89
+ };
90
+ }
91
+ async function managementRequest(config, url, method, body, timeoutMs) {
92
+ const headers = { accept: "application/json" };
93
+ if (config.username !== null) {
94
+ headers.authorization = `Basic ${Buffer.from(`${config.username}:${config.password ?? ""}`, "utf8").toString("base64")}`;
95
+ }
96
+ const payload = body === void 0 ? void 0 : JSON.stringify(body);
97
+ if (payload !== void 0) headers["content-type"] = "application/json";
98
+ if (!config.useTls) {
99
+ const controller = new AbortController();
100
+ const timeout = setTimeout(() => controller.abort(), timeoutMs);
101
+ try {
102
+ return await fetch(url, { method, headers, body: payload, signal: controller.signal });
103
+ } finally {
104
+ clearTimeout(timeout);
105
+ }
106
+ }
107
+ const options = trustOptions(config);
108
+ if (new URL(url).protocol !== "https:") {
109
+ throw new TrustConfigurationError("TLS is enabled: the RabbitMQ management endpoint must use HTTPS.");
110
+ }
111
+ return new Promise((resolve, reject) => {
112
+ const req = (0, import_node_https.request)(url, { ...options, agent: false, method, headers }, (res) => {
113
+ const chunks = [];
114
+ let bytes = 0;
115
+ const maximumBytes = 8 * 1024 * 1024;
116
+ res.on("data", (chunk) => {
117
+ bytes += chunk.length;
118
+ if (bytes > maximumBytes) {
119
+ chunks.length = 0;
120
+ req.destroy(Object.assign(new Error(), { code: "RESPONSE_TOO_LARGE" }));
121
+ return;
122
+ }
123
+ chunks.push(Buffer.from(chunk));
124
+ });
125
+ res.on("error", reject);
126
+ res.on("aborted", () => reject(new Error("Response aborted")));
127
+ res.on("end", () => {
128
+ if (bytes > maximumBytes) return;
129
+ const status = res.statusCode ?? 502;
130
+ try {
131
+ if (status < 200 || status > 599) throw new Error();
132
+ resolve(
133
+ new Response([204, 205, 304].includes(status) ? null : Buffer.concat(chunks).toString("utf8"), { status })
134
+ );
135
+ } catch {
136
+ reject(Object.assign(new Error(), { code: "INVALID_RESPONSE_STATUS" }));
137
+ }
138
+ });
139
+ });
140
+ const timeout = setTimeout(() => req.destroy(Object.assign(new Error(), { code: "ETIMEDOUT" })), timeoutMs);
141
+ req.on("error", reject);
142
+ req.on("close", () => clearTimeout(timeout));
143
+ req.end(payload);
144
+ });
145
+ }
146
+ function describeManagementError(error, timeoutMs) {
147
+ if (error instanceof TrustConfigurationError) return error.message;
148
+ const outer = error;
149
+ const code = outer?.cause?.code ?? outer?.code;
150
+ if (outer?.name === "AbortError" || code === "ETIMEDOUT")
151
+ return `Management API did not respond within ${timeoutMs}ms.`;
152
+ switch (code) {
153
+ case "RESPONSE_TOO_LARGE":
154
+ return "Management API response exceeded the 8 MiB limit.";
155
+ case "INVALID_RESPONSE_STATUS":
156
+ return "Management API returned an invalid HTTP status.";
157
+ case "CERT_HAS_EXPIRED":
158
+ case "CERT_NOT_YET_VALID":
159
+ return "TLS certificate is expired or not yet valid. Check the Attraccess host clock and the certificate validity dates; renew the certificate if necessary.";
160
+ case "ERR_TLS_CERT_ALTNAME_INVALID":
161
+ return "TLS certificate hostname mismatch. Configure tlsServername to match the management certificate DNS name or correct the certificate.";
162
+ case "DEPTH_ZERO_SELF_SIGNED_CERT":
163
+ case "SELF_SIGNED_CERT_IN_CHAIN":
164
+ case "UNABLE_TO_VERIFY_LEAF_SIGNATURE":
165
+ case "UNABLE_TO_GET_ISSUER_CERT":
166
+ case "UNABLE_TO_GET_ISSUER_CERT_LOCALLY":
167
+ return "TLS certificate is not trusted. Configure the issuing CA PEM bundle and ensure the management server sends its intermediate certificates.";
168
+ case "ECONNREFUSED":
169
+ return "Management connection refused. Check the management listener and port.";
170
+ case "ENOTFOUND":
171
+ case "EAI_AGAIN":
172
+ return "Management hostname could not be resolved. Check DNS and the configured host.";
173
+ default:
174
+ return "Failed to reach the RabbitMQ management API. Check connectivity and, for HTTPS, the CA, certificate validity, host clock, and TLS server name.";
175
+ }
176
+ }
177
+
178
+ // backend/rabbitmq-detection.service.ts
179
+ var PLUGIN_CONTEXT = /* @__PURE__ */ Symbol.for("attraccess.plugin.context");
180
+ var CACHE_TTL_MS = 6e4;
181
+ var PROBE_TIMEOUT_MS = 5e3;
182
+ var RabbitmqDetectionService = class {
183
+ constructor(context) {
184
+ __publicField(this, "context", context);
185
+ // Verdicts keyed by MQTT server id. In-memory only; a restart re-probes.
186
+ __publicField(this, "cache", /* @__PURE__ */ new Map());
187
+ }
188
+ // Returns a cached verdict when fresh, otherwise probes. `forceRefresh`
189
+ // bypasses the cache (used by the UI's manual refresh).
190
+ async detect(mqttServerId, forceRefresh = false) {
191
+ const config = await this.context.getMqttServerConfig(mqttServerId);
192
+ const configKey = (0, import_node_crypto2.createHash)("sha256").update(JSON.stringify(config ?? null)).digest("hex");
193
+ if (!forceRefresh) {
194
+ const cached = this.cache.get(mqttServerId);
195
+ if (cached && cached.configKey === configKey && cached.expiresAt > this.now()) {
196
+ return cached.result;
197
+ }
198
+ }
199
+ const result = await this.probe(mqttServerId, config);
200
+ this.cache.set(mqttServerId, { result, configKey, expiresAt: this.now() + CACHE_TTL_MS });
201
+ return result;
202
+ }
203
+ async probe(mqttServerId, config) {
204
+ const checkedAt = this.nowIso();
205
+ if (!config) {
206
+ return {
207
+ mqttServerId,
208
+ isRabbitMQ: false,
209
+ reachable: false,
210
+ authOk: false,
211
+ rabbitmqVersion: null,
212
+ managementVersion: null,
213
+ managementApi: "",
214
+ checkedAt,
215
+ error: "MQTT server not found."
216
+ };
217
+ }
218
+ const managementApi = this.managementApiBase(config);
219
+ let response;
220
+ try {
221
+ response = await this.fetchOverview(managementApi, config);
222
+ } catch (error) {
223
+ return {
224
+ mqttServerId,
225
+ isRabbitMQ: false,
226
+ reachable: false,
227
+ authOk: false,
228
+ rabbitmqVersion: null,
229
+ managementVersion: null,
230
+ managementApi,
231
+ checkedAt,
232
+ error: this.describeError(error)
233
+ };
234
+ }
235
+ if (response.status === 401) {
236
+ return {
237
+ mqttServerId,
238
+ isRabbitMQ: true,
239
+ reachable: true,
240
+ authOk: false,
241
+ rabbitmqVersion: null,
242
+ managementVersion: null,
243
+ managementApi,
244
+ checkedAt,
245
+ error: "Authentication failed (401) against the RabbitMQ management API."
246
+ };
247
+ }
248
+ if (!response.ok) {
249
+ return {
250
+ mqttServerId,
251
+ isRabbitMQ: false,
252
+ reachable: true,
253
+ authOk: false,
254
+ rabbitmqVersion: null,
255
+ managementVersion: null,
256
+ managementApi,
257
+ checkedAt,
258
+ error: `Unexpected response (HTTP ${response.status}) \u2014 not a RabbitMQ management API.`
259
+ };
260
+ }
261
+ const overview = await this.parseOverview(response);
262
+ const isRabbitMQ = overview !== null && typeof overview.rabbitmq_version === "string";
263
+ return {
264
+ mqttServerId,
265
+ isRabbitMQ,
266
+ reachable: true,
267
+ authOk: isRabbitMQ,
268
+ rabbitmqVersion: overview?.rabbitmq_version ?? null,
269
+ managementVersion: overview?.management_version ?? null,
270
+ managementApi,
271
+ checkedAt,
272
+ error: isRabbitMQ ? null : "Reachable, but the response is not a RabbitMQ management API."
273
+ };
274
+ }
275
+ // Builds the management API base URL from the generic MQTT config. The
276
+ // management API uses its configured port or the provider default, independently
277
+ // of the MQTT port. The scheme still comes from useTls.
278
+ managementApiBase(config) {
279
+ return managementApiBase(config);
280
+ }
281
+ async fetchOverview(managementApi, config) {
282
+ return managementRequest(config, `${managementApi}/api/overview`, "GET", void 0, PROBE_TIMEOUT_MS);
283
+ }
284
+ async parseOverview(response) {
285
+ try {
286
+ return await response.json();
287
+ } catch {
288
+ return null;
289
+ }
290
+ }
291
+ describeError(error) {
292
+ return describeManagementError(error, PROBE_TIMEOUT_MS);
293
+ }
294
+ now() {
295
+ return Date.now();
296
+ }
297
+ nowIso() {
298
+ return (/* @__PURE__ */ new Date()).toISOString();
299
+ }
300
+ };
301
+ RabbitmqDetectionService = __decorateClass([
302
+ (0, import_common.Injectable)(),
303
+ __decorateParam(0, (0, import_common.Inject)(PLUGIN_CONTEXT))
304
+ ], RabbitmqDetectionService);
305
+
306
+ // backend/rabbitmq-detection.controller.ts
307
+ var RabbitmqDetectionController = class {
308
+ // esbuild does not emit decorator metadata, so Nest cannot infer constructor
309
+ // types for injection — always inject by an explicit token.
310
+ constructor(detection) {
311
+ __publicField(this, "detection", detection);
312
+ }
313
+ detect(mqttServerId, refresh) {
314
+ return this.detection.detect(mqttServerId, refresh === "true");
315
+ }
316
+ };
317
+ __decorateClass([
318
+ (0, import_common2.Get)("detection/:mqttServerId"),
319
+ __decorateParam(0, (0, import_common2.Param)("mqttServerId", import_common2.ParseIntPipe)),
320
+ __decorateParam(1, (0, import_common2.Query)("refresh"))
321
+ ], RabbitmqDetectionController.prototype, "detect", 1);
322
+ RabbitmqDetectionController = __decorateClass([
323
+ (0, import_plugins_backend_sdk.Auth)("resources.update"),
324
+ (0, import_common2.Controller)("rabbitmq"),
325
+ __decorateParam(0, (0, import_common2.Inject)(RabbitmqDetectionService))
326
+ ], RabbitmqDetectionController);
327
+
328
+ // backend/rabbitmq-management-client.ts
329
+ var import_common3 = require("@nestjs/common");
330
+ var REQUEST_TIMEOUT_MS = 1e4;
331
+ var RabbitmqManagementClient = class {
332
+ // Builds the management API base URL from the generic MQTT config (same
333
+ // convention as RabbitmqDetectionService).
334
+ managementApiBase(config) {
335
+ return managementApiBase(config);
336
+ }
337
+ // Performs one management API request. `path` is relative to `/api`, with
338
+ // every variable segment already URL-encoded by the caller. Returns the
339
+ // parsed JSON body (or null for empty responses, e.g. 201/204).
340
+ async request(config, method, path, body) {
341
+ const base = this.managementApiBase(config);
342
+ let response;
343
+ try {
344
+ response = await managementRequest(config, `${base}/api${path}`, method, body, REQUEST_TIMEOUT_MS);
345
+ } catch (error) {
346
+ throw new import_common3.HttpException(
347
+ `Cannot reach the RabbitMQ management API at ${base}: ${describeManagementError(error, REQUEST_TIMEOUT_MS)}`,
348
+ import_common3.HttpStatus.BAD_GATEWAY
349
+ );
350
+ }
351
+ if (!response.ok) {
352
+ throw await this.toHttpException(response);
353
+ }
354
+ return this.parseBody(response);
355
+ }
356
+ // Maps an upstream error response to an exception with an actionable
357
+ // message. Auth failures surface as 502 (not 401/403) so the host frontend
358
+ // doesn't mistake them for an expired Attraccess session.
359
+ async toHttpException(response) {
360
+ const upstream = await this.parseErrorBody(response);
361
+ const reason = upstream?.reason || upstream?.error || "";
362
+ if (response.status === 401) {
363
+ return new import_common3.HttpException(
364
+ "The RabbitMQ management API rejected the configured MQTT server credentials (401). Check the username/password configured for this MQTT server.",
365
+ import_common3.HttpStatus.BAD_GATEWAY
366
+ );
367
+ }
368
+ if (response.status === 403 || /not.?authori[sz]ed/i.test(reason)) {
369
+ return new import_common3.HttpException(
370
+ 'The configured MQTT server user lacks management privileges on RabbitMQ. User management requires a user with the "administrator" tag.',
371
+ import_common3.HttpStatus.BAD_GATEWAY
372
+ );
373
+ }
374
+ if (response.status === 404) {
375
+ return new import_common3.HttpException("Not found on the RabbitMQ side.", import_common3.HttpStatus.NOT_FOUND);
376
+ }
377
+ if (response.status === 400) {
378
+ return new import_common3.HttpException("RabbitMQ rejected the request.", import_common3.HttpStatus.BAD_REQUEST);
379
+ }
380
+ return new import_common3.HttpException(
381
+ `RabbitMQ management API request failed (HTTP ${response.status}).`,
382
+ import_common3.HttpStatus.BAD_GATEWAY
383
+ );
384
+ }
385
+ async parseBody(response) {
386
+ const text = await response.text();
387
+ if (text.length === 0) {
388
+ return null;
389
+ }
390
+ try {
391
+ return JSON.parse(text);
392
+ } catch {
393
+ throw new import_common3.HttpException(
394
+ "The RabbitMQ management API returned a response that is not valid JSON.",
395
+ import_common3.HttpStatus.BAD_GATEWAY
396
+ );
397
+ }
398
+ }
399
+ async parseErrorBody(response) {
400
+ try {
401
+ return await response.json();
402
+ } catch {
403
+ return null;
404
+ }
405
+ }
406
+ };
407
+ RabbitmqManagementClient = __decorateClass([
408
+ (0, import_common3.Injectable)()
409
+ ], RabbitmqManagementClient);
410
+
411
+ // backend/rabbitmq-users.controller.ts
412
+ var import_plugins_backend_sdk2 = require("@attraccess/plugins-backend-sdk");
413
+ var import_common5 = require("@nestjs/common");
414
+
415
+ // backend/rabbitmq-users.service.ts
416
+ var import_common4 = require("@nestjs/common");
417
+ var PLUGIN_CONTEXT2 = /* @__PURE__ */ Symbol.for("attraccess.plugin.context");
418
+ var RabbitmqUsersService = class {
419
+ constructor(context, client) {
420
+ __publicField(this, "context", context);
421
+ __publicField(this, "client", client);
422
+ }
423
+ // Lists all users with their per-vhost permissions, plus the vhost list for
424
+ // the UI's vhost choices. Three management calls, joined here.
425
+ async listUsers(mqttServerId) {
426
+ const config = await this.requireConfig(mqttServerId);
427
+ const [rawUsers, rawPermissions, rawVhosts] = await Promise.all([
428
+ this.client.request(config, "GET", "/users"),
429
+ this.client.request(config, "GET", "/permissions"),
430
+ this.client.request(config, "GET", "/vhosts")
431
+ ]);
432
+ const permissionsByUser = /* @__PURE__ */ new Map();
433
+ for (const permission of rawPermissions ?? []) {
434
+ const list = permissionsByUser.get(permission.user) ?? [];
435
+ list.push({
436
+ vhost: permission.vhost,
437
+ configure: permission.configure,
438
+ write: permission.write,
439
+ read: permission.read
440
+ });
441
+ permissionsByUser.set(permission.user, list);
442
+ }
443
+ const users = (rawUsers ?? []).map((user) => ({
444
+ name: user.name,
445
+ tags: this.normalizeTags(user.tags),
446
+ permissions: permissionsByUser.get(user.name) ?? []
447
+ }));
448
+ users.sort((a, b) => a.name.localeCompare(b.name));
449
+ return {
450
+ mqttServerId,
451
+ users,
452
+ vhosts: (rawVhosts ?? []).map((vhost) => vhost.name).sort((a, b) => a.localeCompare(b))
453
+ };
454
+ }
455
+ // Creates or updates one user, optionally applying per-vhost permissions in
456
+ // the same operation (used by the create form's "default MQTT permissions").
457
+ async upsertUser(mqttServerId, username, dto) {
458
+ this.assertValidName(username, "Username");
459
+ const config = await this.requireConfig(mqttServerId);
460
+ const existing = await this.findUser(config, username);
461
+ const password = dto.password?.length ? dto.password : void 0;
462
+ if (!existing && !password) {
463
+ throw new import_common4.HttpException("Password is required when creating a new user.", import_common4.HttpStatus.BAD_REQUEST);
464
+ }
465
+ const body = { tags: (dto.tags ?? this.normalizeTags(existing?.tags)).join(",") };
466
+ if (password) {
467
+ body.password = password;
468
+ } else if (existing?.password_hash) {
469
+ body.password_hash = existing.password_hash;
470
+ if (existing.hashing_algorithm) {
471
+ body.hashing_algorithm = existing.hashing_algorithm;
472
+ }
473
+ }
474
+ await this.client.request(config, "PUT", `/users/${encodeURIComponent(username)}`, body);
475
+ for (const permission of dto.permissions ?? []) {
476
+ await this.putPermission(config, username, permission);
477
+ }
478
+ }
479
+ async deleteUser(mqttServerId, username) {
480
+ this.assertValidName(username, "Username");
481
+ const config = await this.requireConfig(mqttServerId);
482
+ if (config.username !== null && config.username === username) {
483
+ throw new import_common4.HttpException(
484
+ "Refusing to delete the user the MQTT server connection is configured with.",
485
+ import_common4.HttpStatus.BAD_REQUEST
486
+ );
487
+ }
488
+ await this.client.request(config, "DELETE", `/users/${encodeURIComponent(username)}`);
489
+ }
490
+ async setPermissions(mqttServerId, username, dto) {
491
+ this.assertValidName(username, "Username");
492
+ this.assertValidName(dto.vhost, "Vhost");
493
+ const config = await this.requireConfig(mqttServerId);
494
+ await this.putPermission(config, username, dto);
495
+ }
496
+ async clearPermissions(mqttServerId, username, vhost) {
497
+ this.assertValidName(username, "Username");
498
+ this.assertValidName(vhost, "Vhost");
499
+ const config = await this.requireConfig(mqttServerId);
500
+ await this.client.request(
501
+ config,
502
+ "DELETE",
503
+ `/permissions/${encodeURIComponent(vhost)}/${encodeURIComponent(username)}`
504
+ );
505
+ }
506
+ async putPermission(config, username, permission) {
507
+ await this.client.request(
508
+ config,
509
+ "PUT",
510
+ `/permissions/${encodeURIComponent(permission.vhost)}/${encodeURIComponent(username)}`,
511
+ {
512
+ configure: permission.configure ?? "",
513
+ write: permission.write ?? "",
514
+ read: permission.read ?? ""
515
+ }
516
+ );
517
+ }
518
+ // Reads one user; null when the broker doesn't know the name.
519
+ async findUser(config, username) {
520
+ try {
521
+ return await this.client.request(config, "GET", `/users/${encodeURIComponent(username)}`);
522
+ } catch (error) {
523
+ if (error instanceof import_common4.HttpException && error.getStatus() === import_common4.HttpStatus.NOT_FOUND) {
524
+ return null;
525
+ }
526
+ throw error;
527
+ }
528
+ }
529
+ async requireConfig(mqttServerId) {
530
+ const config = await this.context.getMqttServerConfig(mqttServerId);
531
+ if (!config) {
532
+ throw new import_common4.HttpException("MQTT server not found.", import_common4.HttpStatus.NOT_FOUND);
533
+ }
534
+ return config;
535
+ }
536
+ normalizeTags(tags) {
537
+ if (Array.isArray(tags)) {
538
+ return tags.filter((tag) => tag.length > 0);
539
+ }
540
+ if (typeof tags === "string" && tags.length > 0) {
541
+ return tags.split(",").map((tag) => tag.trim()).filter((tag) => tag.length > 0);
542
+ }
543
+ return [];
544
+ }
545
+ assertValidName(value, label) {
546
+ if (typeof value !== "string" || value.trim().length === 0) {
547
+ throw new import_common4.HttpException(`${label} must not be empty.`, import_common4.HttpStatus.BAD_REQUEST);
548
+ }
549
+ if (value.length > 255) {
550
+ throw new import_common4.HttpException(`${label} must be at most 255 characters.`, import_common4.HttpStatus.BAD_REQUEST);
551
+ }
552
+ }
553
+ };
554
+ RabbitmqUsersService = __decorateClass([
555
+ (0, import_common4.Injectable)(),
556
+ __decorateParam(0, (0, import_common4.Inject)(PLUGIN_CONTEXT2)),
557
+ __decorateParam(1, (0, import_common4.Inject)(RabbitmqManagementClient))
558
+ ], RabbitmqUsersService);
559
+
560
+ // backend/rabbitmq-users.controller.ts
561
+ var RabbitmqUsersController = class {
562
+ // esbuild does not emit decorator metadata, so Nest cannot infer constructor
563
+ // types for injection — always inject by an explicit token.
564
+ constructor(users) {
565
+ __publicField(this, "users", users);
566
+ }
567
+ list(mqttServerId) {
568
+ return this.users.listUsers(mqttServerId);
569
+ }
570
+ upsert(mqttServerId, username, dto) {
571
+ return this.users.upsertUser(mqttServerId, username, dto ?? {});
572
+ }
573
+ remove(mqttServerId, username) {
574
+ return this.users.deleteUser(mqttServerId, username);
575
+ }
576
+ setPermissions(mqttServerId, username, dto) {
577
+ if (!dto || typeof dto.vhost !== "string") {
578
+ throw new import_common5.HttpException("A vhost is required.", import_common5.HttpStatus.BAD_REQUEST);
579
+ }
580
+ return this.users.setPermissions(mqttServerId, username, dto);
581
+ }
582
+ clearPermissions(mqttServerId, username, vhost) {
583
+ if (typeof vhost !== "string" || vhost.length === 0) {
584
+ throw new import_common5.HttpException("A vhost query parameter is required.", import_common5.HttpStatus.BAD_REQUEST);
585
+ }
586
+ return this.users.clearPermissions(mqttServerId, username, vhost);
587
+ }
588
+ };
589
+ __decorateClass([
590
+ (0, import_common5.Get)("users/:mqttServerId"),
591
+ __decorateParam(0, (0, import_common5.Param)("mqttServerId", import_common5.ParseIntPipe))
592
+ ], RabbitmqUsersController.prototype, "list", 1);
593
+ __decorateClass([
594
+ (0, import_common5.Put)("users/:mqttServerId/:username"),
595
+ __decorateParam(0, (0, import_common5.Param)("mqttServerId", import_common5.ParseIntPipe)),
596
+ __decorateParam(1, (0, import_common5.Param)("username")),
597
+ __decorateParam(2, (0, import_common5.Body)())
598
+ ], RabbitmqUsersController.prototype, "upsert", 1);
599
+ __decorateClass([
600
+ (0, import_common5.Delete)("users/:mqttServerId/:username"),
601
+ __decorateParam(0, (0, import_common5.Param)("mqttServerId", import_common5.ParseIntPipe)),
602
+ __decorateParam(1, (0, import_common5.Param)("username"))
603
+ ], RabbitmqUsersController.prototype, "remove", 1);
604
+ __decorateClass([
605
+ (0, import_common5.Put)("users/:mqttServerId/:username/permissions"),
606
+ __decorateParam(0, (0, import_common5.Param)("mqttServerId", import_common5.ParseIntPipe)),
607
+ __decorateParam(1, (0, import_common5.Param)("username")),
608
+ __decorateParam(2, (0, import_common5.Body)())
609
+ ], RabbitmqUsersController.prototype, "setPermissions", 1);
610
+ __decorateClass([
611
+ (0, import_common5.Delete)("users/:mqttServerId/:username/permissions"),
612
+ __decorateParam(0, (0, import_common5.Param)("mqttServerId", import_common5.ParseIntPipe)),
613
+ __decorateParam(1, (0, import_common5.Param)("username")),
614
+ __decorateParam(2, (0, import_common5.Query)("vhost"))
615
+ ], RabbitmqUsersController.prototype, "clearPermissions", 1);
616
+ RabbitmqUsersController = __decorateClass([
617
+ (0, import_plugins_backend_sdk2.Auth)("resources.update"),
618
+ (0, import_common5.Controller)("rabbitmq"),
619
+ __decorateParam(0, (0, import_common5.Inject)(RabbitmqUsersService))
620
+ ], RabbitmqUsersController);
621
+
622
+ // backend/rabbitmq-credential-provisioning.provider.ts
623
+ var import_crypto = require("crypto");
624
+ var import_common6 = require("@nestjs/common");
625
+
626
+ // ../../../node_modules/.pnpm/async-mutex@0.5.0/node_modules/async-mutex/index.mjs
627
+ var E_TIMEOUT = new Error("timeout while waiting for mutex to become available");
628
+ var E_ALREADY_LOCKED = new Error("mutex already locked");
629
+ var E_CANCELED = new Error("request for lock canceled");
630
+ var __awaiter$2 = function(thisArg, _arguments, P, generator) {
631
+ function adopt(value) {
632
+ return value instanceof P ? value : new P(function(resolve) {
633
+ resolve(value);
634
+ });
635
+ }
636
+ return new (P || (P = Promise))(function(resolve, reject) {
637
+ function fulfilled(value) {
638
+ try {
639
+ step(generator.next(value));
640
+ } catch (e) {
641
+ reject(e);
642
+ }
643
+ }
644
+ function rejected(value) {
645
+ try {
646
+ step(generator["throw"](value));
647
+ } catch (e) {
648
+ reject(e);
649
+ }
650
+ }
651
+ function step(result) {
652
+ result.done ? resolve(result.value) : adopt(result.value).then(fulfilled, rejected);
653
+ }
654
+ step((generator = generator.apply(thisArg, _arguments || [])).next());
655
+ });
656
+ };
657
+ var Semaphore = class {
658
+ constructor(_value, _cancelError = E_CANCELED) {
659
+ this._value = _value;
660
+ this._cancelError = _cancelError;
661
+ this._queue = [];
662
+ this._weightedWaiters = [];
663
+ }
664
+ acquire(weight = 1, priority = 0) {
665
+ if (weight <= 0)
666
+ throw new Error(`invalid weight ${weight}: must be positive`);
667
+ return new Promise((resolve, reject) => {
668
+ const task = { resolve, reject, weight, priority };
669
+ const i = findIndexFromEnd(this._queue, (other) => priority <= other.priority);
670
+ if (i === -1 && weight <= this._value) {
671
+ this._dispatchItem(task);
672
+ } else {
673
+ this._queue.splice(i + 1, 0, task);
674
+ }
675
+ });
676
+ }
677
+ runExclusive(callback_1) {
678
+ return __awaiter$2(this, arguments, void 0, function* (callback, weight = 1, priority = 0) {
679
+ const [value, release] = yield this.acquire(weight, priority);
680
+ try {
681
+ return yield callback(value);
682
+ } finally {
683
+ release();
684
+ }
685
+ });
686
+ }
687
+ waitForUnlock(weight = 1, priority = 0) {
688
+ if (weight <= 0)
689
+ throw new Error(`invalid weight ${weight}: must be positive`);
690
+ if (this._couldLockImmediately(weight, priority)) {
691
+ return Promise.resolve();
692
+ } else {
693
+ return new Promise((resolve) => {
694
+ if (!this._weightedWaiters[weight - 1])
695
+ this._weightedWaiters[weight - 1] = [];
696
+ insertSorted(this._weightedWaiters[weight - 1], { resolve, priority });
697
+ });
698
+ }
699
+ }
700
+ isLocked() {
701
+ return this._value <= 0;
702
+ }
703
+ getValue() {
704
+ return this._value;
705
+ }
706
+ setValue(value) {
707
+ this._value = value;
708
+ this._dispatchQueue();
709
+ }
710
+ release(weight = 1) {
711
+ if (weight <= 0)
712
+ throw new Error(`invalid weight ${weight}: must be positive`);
713
+ this._value += weight;
714
+ this._dispatchQueue();
715
+ }
716
+ cancel() {
717
+ this._queue.forEach((entry) => entry.reject(this._cancelError));
718
+ this._queue = [];
719
+ }
720
+ _dispatchQueue() {
721
+ this._drainUnlockWaiters();
722
+ while (this._queue.length > 0 && this._queue[0].weight <= this._value) {
723
+ this._dispatchItem(this._queue.shift());
724
+ this._drainUnlockWaiters();
725
+ }
726
+ }
727
+ _dispatchItem(item) {
728
+ const previousValue = this._value;
729
+ this._value -= item.weight;
730
+ item.resolve([previousValue, this._newReleaser(item.weight)]);
731
+ }
732
+ _newReleaser(weight) {
733
+ let called = false;
734
+ return () => {
735
+ if (called)
736
+ return;
737
+ called = true;
738
+ this.release(weight);
739
+ };
740
+ }
741
+ _drainUnlockWaiters() {
742
+ if (this._queue.length === 0) {
743
+ for (let weight = this._value; weight > 0; weight--) {
744
+ const waiters = this._weightedWaiters[weight - 1];
745
+ if (!waiters)
746
+ continue;
747
+ waiters.forEach((waiter) => waiter.resolve());
748
+ this._weightedWaiters[weight - 1] = [];
749
+ }
750
+ } else {
751
+ const queuedPriority = this._queue[0].priority;
752
+ for (let weight = this._value; weight > 0; weight--) {
753
+ const waiters = this._weightedWaiters[weight - 1];
754
+ if (!waiters)
755
+ continue;
756
+ const i = waiters.findIndex((waiter) => waiter.priority <= queuedPriority);
757
+ (i === -1 ? waiters : waiters.splice(0, i)).forEach(((waiter) => waiter.resolve()));
758
+ }
759
+ }
760
+ }
761
+ _couldLockImmediately(weight, priority) {
762
+ return (this._queue.length === 0 || this._queue[0].priority < priority) && weight <= this._value;
763
+ }
764
+ };
765
+ function insertSorted(a, v) {
766
+ const i = findIndexFromEnd(a, (other) => v.priority <= other.priority);
767
+ a.splice(i + 1, 0, v);
768
+ }
769
+ function findIndexFromEnd(a, predicate) {
770
+ for (let i = a.length - 1; i >= 0; i--) {
771
+ if (predicate(a[i])) {
772
+ return i;
773
+ }
774
+ }
775
+ return -1;
776
+ }
777
+ var __awaiter$1 = function(thisArg, _arguments, P, generator) {
778
+ function adopt(value) {
779
+ return value instanceof P ? value : new P(function(resolve) {
780
+ resolve(value);
781
+ });
782
+ }
783
+ return new (P || (P = Promise))(function(resolve, reject) {
784
+ function fulfilled(value) {
785
+ try {
786
+ step(generator.next(value));
787
+ } catch (e) {
788
+ reject(e);
789
+ }
790
+ }
791
+ function rejected(value) {
792
+ try {
793
+ step(generator["throw"](value));
794
+ } catch (e) {
795
+ reject(e);
796
+ }
797
+ }
798
+ function step(result) {
799
+ result.done ? resolve(result.value) : adopt(result.value).then(fulfilled, rejected);
800
+ }
801
+ step((generator = generator.apply(thisArg, _arguments || [])).next());
802
+ });
803
+ };
804
+ var Mutex = class {
805
+ constructor(cancelError) {
806
+ this._semaphore = new Semaphore(1, cancelError);
807
+ }
808
+ acquire() {
809
+ return __awaiter$1(this, arguments, void 0, function* (priority = 0) {
810
+ const [, releaser] = yield this._semaphore.acquire(1, priority);
811
+ return releaser;
812
+ });
813
+ }
814
+ runExclusive(callback, priority = 0) {
815
+ return this._semaphore.runExclusive(() => callback(), 1, priority);
816
+ }
817
+ isLocked() {
818
+ return this._semaphore.isLocked();
819
+ }
820
+ waitForUnlock(priority = 0) {
821
+ return this._semaphore.waitForUnlock(1, priority);
822
+ }
823
+ release() {
824
+ if (this._semaphore.isLocked())
825
+ this._semaphore.release();
826
+ }
827
+ cancel() {
828
+ return this._semaphore.cancel();
829
+ }
830
+ };
831
+
832
+ // backend/rabbitmq-credential-provisioning.provider.ts
833
+ var _RabbitmqCredentialProvisioningProvider = class _RabbitmqCredentialProvisioningProvider {
834
+ constructor(context) {
835
+ __publicField(this, "context", context);
836
+ __publicField(this, "id", "rabbitmq");
837
+ __publicField(this, "displayName", "RabbitMQ Management API");
838
+ __publicField(this, "client", new RabbitmqManagementClient());
839
+ __publicField(this, "detection");
840
+ this.detection = new RabbitmqDetectionService(context);
841
+ }
842
+ async supports(config) {
843
+ return (await this.detection.detect(config.id)).isRabbitMQ;
844
+ }
845
+ provision(request2) {
846
+ return this.writeCredential(request2);
847
+ }
848
+ rotate(request2) {
849
+ return this.writeCredential(request2);
850
+ }
851
+ async revoke(request2) {
852
+ this.assertName(request2.username, "Username");
853
+ const config = await this.requireConfig(request2.mqttServerId);
854
+ this.assertNotManagementUser(config, request2.username);
855
+ try {
856
+ await this.client.request(config, "DELETE", `/users/${encodeURIComponent(request2.username)}`);
857
+ } catch (error) {
858
+ if (error instanceof import_common6.HttpException && error.getStatus() === import_common6.HttpStatus.NOT_FOUND) return;
859
+ throw error;
860
+ }
861
+ }
862
+ async writeCredential(request2) {
863
+ this.assertRequest(request2);
864
+ const config = await this.requireConfig(request2.mqttServerId);
865
+ this.assertNotManagementUser(config, request2.username);
866
+ const lockKey = `${config.id}:${request2.vhost}`;
867
+ let lock = _RabbitmqCredentialProvisioningProvider.vhostLocks.get(lockKey);
868
+ if (!lock) {
869
+ lock = { mutex: new Mutex(), users: 0 };
870
+ _RabbitmqCredentialProvisioningProvider.vhostLocks.set(lockKey, lock);
871
+ }
872
+ lock.users += 1;
873
+ try {
874
+ return await lock.mutex.runExclusive(() => this.writeCredentialLocked(request2, config));
875
+ } finally {
876
+ lock.users -= 1;
877
+ if (lock.users === 0 && _RabbitmqCredentialProvisioningProvider.vhostLocks.get(lockKey) === lock) {
878
+ _RabbitmqCredentialProvisioningProvider.vhostLocks.delete(lockKey);
879
+ }
880
+ }
881
+ }
882
+ async writeCredentialLocked(request2, config) {
883
+ const password = (0, import_crypto.randomBytes)(24).toString("base64url");
884
+ const vhost = encodeURIComponent(request2.vhost);
885
+ const username = encodeURIComponent(request2.username);
886
+ const existing = await this.userExists(config, username);
887
+ const vhostExisted = await this.exists(config, `/vhosts/${vhost}`);
888
+ await this.client.request(config, "PUT", `/vhosts/${vhost}`);
889
+ let previousPermissions = null;
890
+ let previousTopicPermissions = null;
891
+ let permissionsCaptured = false;
892
+ try {
893
+ if (existing) {
894
+ previousPermissions = await this.getPermissions(config, vhost, username);
895
+ previousTopicPermissions = await this.getTopicPermissions(config, vhost, username);
896
+ permissionsCaptured = true;
897
+ await this.writePermissions(config, request2, vhost, username);
898
+ await this.client.request(config, "PUT", `/users/${username}`, { password, tags: "" });
899
+ } else {
900
+ await this.client.request(config, "PUT", `/users/${username}`, { password, tags: "" });
901
+ await this.writePermissions(config, request2, vhost, username);
902
+ }
903
+ } catch (error) {
904
+ const rollback = [
905
+ ...existing && permissionsCaptured ? [() => this.restorePermissions(config, vhost, username, previousPermissions, previousTopicPermissions)] : !existing ? [() => this.client.request(config, "DELETE", `/users/${username}`)] : [],
906
+ ...!vhostExisted ? [() => this.client.request(config, "DELETE", `/vhosts/${vhost}`)] : []
907
+ ];
908
+ await this.failAfterRollback(error, rollback);
909
+ }
910
+ return {
911
+ providerId: this.id,
912
+ identity: request2.identity,
913
+ username: request2.username,
914
+ vhost: request2.vhost,
915
+ password
916
+ };
917
+ }
918
+ async writePermissions(config, request2, vhost, username) {
919
+ const subscriptionQueue = `mqtt-subscription-${escapeRegex(request2.identity)}.*`;
920
+ await this.client.request(config, "PUT", `/permissions/${vhost}/${username}`, {
921
+ configure: `^${subscriptionQueue}$`,
922
+ write: `^(amq\\.topic|${subscriptionQueue})$`,
923
+ read: `^(amq\\.topic|${subscriptionQueue})$`
924
+ });
925
+ await this.client.request(config, "PUT", `/topic-permissions/${vhost}/${username}`, {
926
+ exchange: "amq.topic",
927
+ write: mqttFiltersToRegex(request2.topicPolicy.publish),
928
+ read: mqttFiltersToRegex(request2.topicPolicy.subscribe)
929
+ });
930
+ }
931
+ async userExists(config, username) {
932
+ return this.exists(config, `/users/${username}`);
933
+ }
934
+ async exists(config, path) {
935
+ try {
936
+ await this.client.request(config, "GET", path);
937
+ return true;
938
+ } catch (error) {
939
+ if (error instanceof import_common6.HttpException && error.getStatus() === import_common6.HttpStatus.NOT_FOUND) {
940
+ return false;
941
+ }
942
+ throw error;
943
+ }
944
+ }
945
+ async getPermissions(config, vhost, username) {
946
+ return this.getOptional(config, `/permissions/${vhost}/${username}`);
947
+ }
948
+ async getTopicPermissions(config, vhost, username) {
949
+ return this.getOptional(config, `/topic-permissions/${vhost}/${username}`);
950
+ }
951
+ async getOptional(config, path) {
952
+ try {
953
+ return await this.client.request(config, "GET", path);
954
+ } catch (error) {
955
+ if (error instanceof import_common6.HttpException && error.getStatus() === import_common6.HttpStatus.NOT_FOUND) {
956
+ return null;
957
+ }
958
+ throw error;
959
+ }
960
+ }
961
+ async restorePermissions(config, vhost, username, permissions, topicPermissions) {
962
+ const results = await Promise.allSettled([
963
+ this.restorePermission(config, `/permissions/${vhost}/${username}`, permissions),
964
+ this.restorePermission(config, `/topic-permissions/${vhost}/${username}`, topicPermissions)
965
+ ]);
966
+ const failures = results.filter((result) => result.status === "rejected").map((result) => result.reason);
967
+ if (failures.length > 0) {
968
+ throw new AggregateError(failures, "Failed to restore RabbitMQ permissions.");
969
+ }
970
+ }
971
+ async restorePermission(config, path, permissions) {
972
+ await this.client.request(config, permissions ? "PUT" : "DELETE", path, permissions ?? void 0);
973
+ }
974
+ async failAfterRollback(error, rollback) {
975
+ const results = await Promise.allSettled(rollback.map((operation) => operation()));
976
+ const failures = results.filter((result) => result.status === "rejected").map((result) => result.reason);
977
+ if (failures.length > 0) {
978
+ throw new AggregateError([error, ...failures], "Credential provisioning failed and rollback was incomplete.");
979
+ }
980
+ throw error;
981
+ }
982
+ async requireConfig(mqttServerId) {
983
+ const config = await this.context.getMqttServerConfig(mqttServerId);
984
+ if (!config) {
985
+ throw new import_common6.BadRequestException("MQTT server not found.");
986
+ }
987
+ return config;
988
+ }
989
+ assertRequest(request2) {
990
+ this.assertName(request2.identity, "Identity");
991
+ this.assertName(request2.username, "Username");
992
+ this.assertName(request2.vhost, "Vhost");
993
+ if (!Array.isArray(request2.topicPolicy.publish) || !Array.isArray(request2.topicPolicy.subscribe)) {
994
+ throw new import_common6.BadRequestException("Publish and subscribe topic policies must be arrays.");
995
+ }
996
+ for (const filter of [...request2.topicPolicy.publish, ...request2.topicPolicy.subscribe]) {
997
+ if (typeof filter !== "string" || filter.length === 0 || filter.length > 1024) {
998
+ throw new import_common6.BadRequestException("MQTT topic filters must be non-empty strings no longer than 1024 characters.");
999
+ }
1000
+ this.assertTopicFilter(filter);
1001
+ }
1002
+ }
1003
+ assertTopicFilter(filter) {
1004
+ if (filter.includes(".")) {
1005
+ throw new import_common6.BadRequestException("RabbitMQ MQTT topic policies cannot contain dots.");
1006
+ }
1007
+ const segments = filter.split("/");
1008
+ for (const [index, segment] of segments.entries()) {
1009
+ if (segment.includes("+") && segment !== "+" || segment.includes("#") && (segment !== "#" || index !== segments.length - 1)) {
1010
+ throw new import_common6.BadRequestException(
1011
+ "MQTT wildcards must occupy a complete topic level, and # must be the final level."
1012
+ );
1013
+ }
1014
+ }
1015
+ }
1016
+ assertName(value, label) {
1017
+ if (typeof value !== "string" || value.trim().length === 0 || value.length > 255) {
1018
+ throw new import_common6.BadRequestException(`${label} must be a non-empty string no longer than 255 characters.`);
1019
+ }
1020
+ }
1021
+ assertNotManagementUser(config, username) {
1022
+ if (config.username === username) {
1023
+ throw new import_common6.BadRequestException("Refusing to modify the MQTT server management identity.");
1024
+ }
1025
+ }
1026
+ };
1027
+ __publicField(_RabbitmqCredentialProvisioningProvider, "vhostLocks", /* @__PURE__ */ new Map());
1028
+ var RabbitmqCredentialProvisioningProvider = _RabbitmqCredentialProvisioningProvider;
1029
+ function mqttFiltersToRegex(filters) {
1030
+ if (filters.length === 0) {
1031
+ return "$(?!)";
1032
+ }
1033
+ return `^(?:${filters.map(mqttFilterToRegex).join("|")})$`;
1034
+ }
1035
+ function mqttFilterToRegex(filter) {
1036
+ const terminalMultiLevel = filter.endsWith("/#");
1037
+ const prefix = terminalMultiLevel ? filter.slice(0, -2) : filter;
1038
+ const translated = prefix.split("/").map((segment) => {
1039
+ if (segment === "+") {
1040
+ return "[^.]+";
1041
+ }
1042
+ if (segment === "#") {
1043
+ return ".*";
1044
+ }
1045
+ return segment.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
1046
+ }).join("\\.");
1047
+ return terminalMultiLevel ? `${translated}(?:\\..*)?` : translated;
1048
+ }
1049
+ function escapeRegex(value) {
1050
+ return value.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
1051
+ }
1052
+
1053
+ // backend/plugin.ts
1054
+ var PLUGIN_CONTEXT3 = /* @__PURE__ */ Symbol.for("attraccess.plugin.context");
1055
+ var RabbitmqService = class {
1056
+ constructor(context) {
1057
+ __publicField(this, "context", context);
1058
+ }
1059
+ onModuleInit() {
1060
+ this.context.logger.log("RabbitMQ plugin loaded \u2014 MQTT-server RabbitMQ detection active.");
1061
+ }
1062
+ getStatus() {
1063
+ return { plugin: "rabbitmq", status: "ok" };
1064
+ }
1065
+ };
1066
+ RabbitmqService = __decorateClass([
1067
+ (0, import_common7.Injectable)(),
1068
+ __decorateParam(0, (0, import_common7.Inject)(PLUGIN_CONTEXT3))
1069
+ ], RabbitmqService);
1070
+ var RabbitmqController = class {
1071
+ // esbuild does not emit decorator metadata, so Nest cannot infer constructor
1072
+ // types for injection — always inject by an explicit token.
1073
+ constructor(service) {
1074
+ __publicField(this, "service", service);
1075
+ }
1076
+ status() {
1077
+ return this.service.getStatus();
1078
+ }
1079
+ };
1080
+ __decorateClass([
1081
+ (0, import_common7.Get)("status")
1082
+ ], RabbitmqController.prototype, "status", 1);
1083
+ RabbitmqController = __decorateClass([
1084
+ (0, import_plugins_backend_sdk3.Auth)("resources.update"),
1085
+ (0, import_common7.Controller)("rabbitmq"),
1086
+ __decorateParam(0, (0, import_common7.Inject)(RabbitmqService))
1087
+ ], RabbitmqController);
1088
+ var RabbitmqPluginModule = class {
1089
+ };
1090
+ var plugin = {
1091
+ credentialProvisioningProvider(context) {
1092
+ return new RabbitmqCredentialProvisioningProvider(context);
1093
+ },
1094
+ register(context) {
1095
+ return {
1096
+ module: RabbitmqPluginModule,
1097
+ controllers: [RabbitmqController, RabbitmqDetectionController, RabbitmqUsersController],
1098
+ providers: [
1099
+ { provide: PLUGIN_CONTEXT3, useValue: context },
1100
+ RabbitmqService,
1101
+ RabbitmqDetectionService,
1102
+ RabbitmqManagementClient,
1103
+ RabbitmqUsersService
1104
+ ]
1105
+ };
1106
+ }
1107
+ };
1108
+ var plugin_default = plugin;