@discover-cloud/shared 1.2.9 → 1.4.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.
Files changed (183) hide show
  1. package/dist/authorization/index.d.ts +2 -2
  2. package/dist/authorization/index.js +1 -1
  3. package/dist/authorization/permission-cache.service.d.ts +104 -16
  4. package/dist/authorization/permission-cache.service.js +177 -148
  5. package/dist/authorization/permission-checker.d.ts +30 -0
  6. package/dist/authorization/permission-checker.js +40 -0
  7. package/dist/context/index.d.ts +1 -1
  8. package/dist/context/index.js +1 -1
  9. package/dist/context/request-context.storage.d.ts +21 -0
  10. package/dist/context/request-context.storage.js +37 -0
  11. package/dist/contracts/auth-service/account.dto.d.ts +30 -0
  12. package/dist/contracts/auth-service/account.dto.js +7 -0
  13. package/dist/contracts/auth-service/auth.dto.d.ts +21 -0
  14. package/dist/contracts/auth-service/auth.dto.js +10 -0
  15. package/dist/contracts/index.d.ts +1 -0
  16. package/dist/{security → contracts}/index.js +1 -1
  17. package/dist/contracts/visibility.d.ts +63 -0
  18. package/dist/contracts/visibility.js +10 -0
  19. package/dist/dtos/audit-service.types.d.ts +42 -0
  20. package/dist/dtos/audit-service.types.js +31 -0
  21. package/dist/dtos/auth-service.dto.d.ts +87 -26
  22. package/dist/dtos/auth-service.dto.js +4 -0
  23. package/dist/dtos/cloud-service.dto.d.ts +57 -27
  24. package/dist/dtos/cloud-service.dto.js +5 -0
  25. package/dist/dtos/index.d.ts +6 -5
  26. package/dist/dtos/index.js +1 -0
  27. package/dist/dtos/insights-service.dto.d.ts +184 -32
  28. package/dist/dtos/insights-service.dto.js +8 -0
  29. package/dist/dtos/response.dto.d.ts +12 -83
  30. package/dist/dtos/response.dto.js +2 -21
  31. package/dist/dtos/user-service.dto.d.ts +163 -13
  32. package/dist/enums/domain.enums.d.ts +272 -75
  33. package/dist/enums/domain.enums.js +329 -102
  34. package/dist/enums/index.d.ts +2 -2
  35. package/dist/enums/permissions.enums.d.ts +142 -80
  36. package/dist/enums/permissions.enums.js +174 -133
  37. package/dist/errors/app-error.d.ts +13 -18
  38. package/dist/errors/app-error.js +15 -21
  39. package/dist/errors/http-errors.d.ts +106 -18
  40. package/dist/errors/http-errors.js +136 -51
  41. package/dist/errors/index.d.ts +2 -2
  42. package/dist/http/index.d.ts +2 -1
  43. package/dist/http/index.js +1 -0
  44. package/dist/http/request-context.d.ts +8 -0
  45. package/dist/http/request-context.js +37 -0
  46. package/dist/http/service-client.d.ts +93 -40
  47. package/dist/http/service-client.js +104 -55
  48. package/dist/index.d.ts +11 -9
  49. package/dist/index.js +2 -0
  50. package/dist/jwt/index.d.ts +3 -2
  51. package/dist/jwt/index.js +2 -1
  52. package/dist/jwt/machine-jwt-verifier.d.ts +21 -0
  53. package/dist/jwt/machine-jwt-verifier.js +83 -0
  54. package/dist/jwt/machine-token-client.d.ts +36 -5
  55. package/dist/jwt/machine-token-client.js +61 -27
  56. package/dist/jwt/user-jwt-verifier.d.ts +31 -0
  57. package/dist/jwt/user-jwt-verifier.js +102 -0
  58. package/dist/messaging/audit.publisher.d.ts +14 -0
  59. package/dist/messaging/audit.publisher.js +49 -0
  60. package/dist/messaging/index.d.ts +2 -0
  61. package/dist/{dto → messaging}/index.js +2 -3
  62. package/dist/messaging/rabbitmq.client.d.ts +15 -0
  63. package/dist/messaging/rabbitmq.client.js +133 -0
  64. package/dist/middleware/error-handler.middleware.d.ts +8 -28
  65. package/dist/middleware/error-handler.middleware.js +63 -54
  66. package/dist/middleware/index.d.ts +10 -8
  67. package/dist/middleware/index.js +7 -5
  68. package/dist/middleware/request-context.middleware.d.ts +6 -0
  69. package/dist/middleware/request-context.middleware.js +48 -0
  70. package/dist/middleware/require-auth.middleware.d.ts +7 -76
  71. package/dist/middleware/require-auth.middleware.js +29 -117
  72. package/dist/middleware/require-machine.middleware.d.ts +13 -0
  73. package/dist/middleware/require-machine.middleware.js +128 -0
  74. package/dist/middleware/require-org-permission-from-body.middleware.d.ts +10 -0
  75. package/dist/middleware/require-org-permission-from-body.middleware.js +23 -0
  76. package/dist/middleware/require-org-permission.middleware.d.ts +19 -0
  77. package/dist/middleware/require-org-permission.middleware.js +115 -0
  78. package/dist/middleware/require-platform-permission.middleware.d.ts +19 -0
  79. package/dist/middleware/require-platform-permission.middleware.js +91 -0
  80. package/dist/middleware/require-user.middleware.d.ts +14 -0
  81. package/dist/middleware/require-user.middleware.js +30 -0
  82. package/dist/middleware/require-workspace-permission.middleware.d.ts +20 -0
  83. package/dist/middleware/require-workspace-permission.middleware.js +114 -0
  84. package/dist/middleware/validate.middleware.d.ts +31 -31
  85. package/dist/middleware/validate.middleware.js +31 -33
  86. package/dist/types/audit.types.d.ts +39 -0
  87. package/dist/types/audit.types.js +30 -0
  88. package/dist/types/express.types.d.ts +92 -124
  89. package/dist/types/express.types.js +30 -49
  90. package/dist/types/index.d.ts +1 -1
  91. package/dist/utils/date.util.d.ts +6 -0
  92. package/dist/utils/date.util.js +11 -0
  93. package/dist/utils/env.util.d.ts +6 -0
  94. package/dist/utils/env.util.js +19 -0
  95. package/dist/utils/index.d.ts +5 -4
  96. package/dist/utils/index.js +5 -4
  97. package/dist/utils/logger.util.d.ts +31 -0
  98. package/dist/utils/logger.util.js +91 -0
  99. package/dist/utils/pagination.util.d.ts +6 -0
  100. package/dist/utils/pagination.util.js +20 -0
  101. package/dist/utils/request-context.als.d.ts +10 -0
  102. package/dist/utils/request-context.als.js +5 -0
  103. package/dist/utils/response.util.d.ts +27 -0
  104. package/dist/utils/response.util.js +56 -0
  105. package/dist/utils/slug.util.d.ts +9 -0
  106. package/dist/utils/slug.util.js +32 -0
  107. package/dist/utils/url-safety.util.d.ts +6 -0
  108. package/dist/utils/url-safety.util.js +75 -0
  109. package/package.json +3 -1
  110. package/dist/authorization/permissions.d.ts +0 -78
  111. package/dist/authorization/permissions.js +0 -174
  112. package/dist/context/access-context.d.ts +0 -10
  113. package/dist/context/access-context.js +0 -2
  114. package/dist/dto/auth-service.dtos.d.ts +0 -44
  115. package/dist/dto/auth-service.dtos.js +0 -2
  116. package/dist/dto/index.d.ts +0 -3
  117. package/dist/dto/response.dtos.d.ts +0 -55
  118. package/dist/dto/response.dtos.js +0 -6
  119. package/dist/dto/user-service.dtos.d.ts +0 -50
  120. package/dist/dto/user-service.dtos.js +0 -2
  121. package/dist/enums/auth-service.enums.d.ts +0 -12
  122. package/dist/enums/auth-service.enums.js +0 -17
  123. package/dist/enums/permissions.types.d.ts +0 -12
  124. package/dist/enums/permissions.types.js +0 -17
  125. package/dist/enums/user-service.enums.d.ts +0 -32
  126. package/dist/enums/user-service.enums.js +0 -41
  127. package/dist/internal/index.d.ts +0 -4
  128. package/dist/internal/index.js +0 -20
  129. package/dist/internal/internal-jwt.service.d.ts +0 -13
  130. package/dist/internal/internal-jwt.service.js +0 -88
  131. package/dist/internal/internal-jwt.types.d.ts +0 -7
  132. package/dist/internal/internal-jwt.types.js +0 -2
  133. package/dist/internal/internal-key-manager.d.ts +0 -16
  134. package/dist/internal/internal-key-manager.js +0 -67
  135. package/dist/internal/registry.d.ts +0 -8
  136. package/dist/internal/registry.js +0 -34
  137. package/dist/internal/service-client.d.ts +0 -9
  138. package/dist/internal/service-client.js +0 -94
  139. package/dist/jwt/internal-jwt-verifier.d.ts +0 -41
  140. package/dist/jwt/internal-jwt-verifier.js +0 -185
  141. package/dist/jwt/jwt-verifier.d.ts +0 -9
  142. package/dist/jwt/jwt-verifier.js +0 -36
  143. package/dist/jwt/service-client.d.ts +0 -7
  144. package/dist/jwt/service-client.js +0 -87
  145. package/dist/middleware/authorize.d.ts +0 -3
  146. package/dist/middleware/authorize.js +0 -24
  147. package/dist/middleware/authorize.middleware.d.ts +0 -54
  148. package/dist/middleware/authorize.middleware.js +0 -104
  149. package/dist/middleware/error-handler.d.ts +0 -4
  150. package/dist/middleware/error-handler.js +0 -23
  151. package/dist/middleware/request-id.d.ts +0 -2
  152. package/dist/middleware/request-id.js +0 -9
  153. package/dist/middleware/request-id.middleware.d.ts +0 -22
  154. package/dist/middleware/request-id.middleware.js +0 -34
  155. package/dist/middleware/require-auth.d.ts +0 -10
  156. package/dist/middleware/require-auth.js +0 -34
  157. package/dist/middleware/require-human.middleware.d.ts +0 -2
  158. package/dist/middleware/require-human.middleware.js +0 -18
  159. package/dist/middleware/require-internal.middleware.d.ts +0 -18
  160. package/dist/middleware/require-internal.middleware.js +0 -183
  161. package/dist/middleware/validate.d.ts +0 -5
  162. package/dist/middleware/validate.js +0 -18
  163. package/dist/middleware/validated-merge.middleware.d.ts +0 -20
  164. package/dist/middleware/validated-merge.middleware.js +0 -33
  165. package/dist/middleware/verify-internal-jwt.d.ts +0 -7
  166. package/dist/middleware/verify-internal-jwt.js +0 -25
  167. package/dist/security/guard.d.ts +0 -10
  168. package/dist/security/guard.js +0 -40
  169. package/dist/security/index.d.ts +0 -1
  170. package/dist/types/express.d.ts +0 -22
  171. package/dist/types/express.js +0 -3
  172. package/dist/utils/date.utils.d.ts +0 -25
  173. package/dist/utils/date.utils.js +0 -30
  174. package/dist/utils/env.d.ts +0 -46
  175. package/dist/utils/env.js +0 -61
  176. package/dist/utils/env.utils.d.ts +0 -46
  177. package/dist/utils/env.utils.js +0 -61
  178. package/dist/utils/logger.utils.d.ts +0 -66
  179. package/dist/utils/logger.utils.js +0 -97
  180. package/dist/utils/response.d.ts +0 -4
  181. package/dist/utils/response.js +0 -35
  182. package/dist/utils/response.utils.d.ts +0 -54
  183. package/dist/utils/response.utils.js +0 -85
@@ -0,0 +1,102 @@
1
+ "use strict";
2
+ /**
3
+ * @file user-jwt-verifier.ts
4
+ * @description Verifies user RS256 access tokens issued by auth-service.
5
+ */
6
+ var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
7
+ if (k2 === undefined) k2 = k;
8
+ var desc = Object.getOwnPropertyDescriptor(m, k);
9
+ if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
10
+ desc = { enumerable: true, get: function() { return m[k]; } };
11
+ }
12
+ Object.defineProperty(o, k2, desc);
13
+ }) : (function(o, m, k, k2) {
14
+ if (k2 === undefined) k2 = k;
15
+ o[k2] = m[k];
16
+ }));
17
+ var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
18
+ Object.defineProperty(o, "default", { enumerable: true, value: v });
19
+ }) : function(o, v) {
20
+ o["default"] = v;
21
+ });
22
+ var __importStar = (this && this.__importStar) || (function () {
23
+ var ownKeys = function(o) {
24
+ ownKeys = Object.getOwnPropertyNames || function (o) {
25
+ var ar = [];
26
+ for (var k in o) if (Object.prototype.hasOwnProperty.call(o, k)) ar[ar.length] = k;
27
+ return ar;
28
+ };
29
+ return ownKeys(o);
30
+ };
31
+ return function (mod) {
32
+ if (mod && mod.__esModule) return mod;
33
+ var result = {};
34
+ if (mod != null) for (var k = ownKeys(mod), i = 0; i < k.length; i++) if (k[i] !== "default") __createBinding(result, mod, k[i]);
35
+ __setModuleDefault(result, mod);
36
+ return result;
37
+ };
38
+ })();
39
+ Object.defineProperty(exports, "__esModule", { value: true });
40
+ exports.UserJwtVerifier = void 0;
41
+ const jose = __importStar(require("jose"));
42
+ const env_util_1 = require("../utils/env.util");
43
+ const CLOCK_TOLERANCE_SECONDS = Number((0, env_util_1.getEnv)('JWT_CLOCK_TOLERANCE_SECONDS'));
44
+ const JWKS_CACHE_MAX_AGE_MS = Number((0, env_util_1.getEnv)('JWKS_CACHE_MAX_AGE_MS'));
45
+ const JWKS_FETCH_TIMEOUT_MS = Number((0, env_util_1.getEnv)('JWKS_FETCH_TIMEOUT_MS'));
46
+ /**
47
+ * Verifies auth-service issued user access tokens.
48
+ */
49
+ class UserJwtVerifier {
50
+ constructor() {
51
+ const authJwksUri = (0, env_util_1.getEnv)('AUTH_JWKS_URI');
52
+ this.issuer = (0, env_util_1.getEnv)('EXTERNAL_JWT_ISSUER');
53
+ this.audience = (0, env_util_1.getEnv)('EXTERNAL_JWT_AUDIENCE');
54
+ this.jwks = jose.createRemoteJWKSet(new URL(authJwksUri), {
55
+ cacheMaxAge: JWKS_CACHE_MAX_AGE_MS,
56
+ timeoutDuration: JWKS_FETCH_TIMEOUT_MS,
57
+ });
58
+ }
59
+ /**
60
+ * Verify JWT signature and required user claims.
61
+ *
62
+ * @param {string} token - Raw bearer access token.
63
+ * @returns {Promise<AccessTokenPayload>} Verified access payload.
64
+ */
65
+ async verify(token) {
66
+ const { payload } = await jose.jwtVerify(token, this.jwks, {
67
+ issuer: this.issuer,
68
+ audience: this.audience,
69
+ algorithms: ['RS256'],
70
+ clockTolerance: CLOCK_TOLERANCE_SECONDS,
71
+ });
72
+ if (payload.typ !== 'access') {
73
+ throw new jose.errors.JWTClaimValidationFailed(`Token type mismatch: expected "access", got "${String(payload.typ)}"`, payload, 'typ', 'check_failed');
74
+ }
75
+ if (typeof payload.accountId !== 'string' ||
76
+ typeof payload.sessionId !== 'string' ||
77
+ typeof payload.jti !== 'string' ||
78
+ typeof payload.iat !== 'number' ||
79
+ typeof payload.exp !== 'number') {
80
+ throw new jose.errors.JWTClaimValidationFailed('Missing required access token claims', payload, 'payload', 'missing');
81
+ }
82
+ return payload;
83
+ }
84
+ /**
85
+ * Builds request authorization context.
86
+ *
87
+ * @param {string} token - Raw bearer access token.
88
+ * @returns {Promise<{ payload: AccessTokenPayload; context: UserContext }>} The auth context block.
89
+ */
90
+ async buildAccessContext(token) {
91
+ const payload = await this.verify(token);
92
+ return {
93
+ payload,
94
+ context: {
95
+ type: 'user',
96
+ accountId: payload.accountId,
97
+ sessionId: payload.sessionId,
98
+ },
99
+ };
100
+ }
101
+ }
102
+ exports.UserJwtVerifier = UserJwtVerifier;
@@ -0,0 +1,14 @@
1
+ /**
2
+ * @file audit.publisher.ts
3
+ * @description Publishes audit events with automatic request context enrichment.
4
+ */
5
+ import { RabbitMQClient } from '../messaging';
6
+ import { RecordAuditActionInput } from '../dtos';
7
+ import { ILogger } from '../utils/logger.util';
8
+ export type PublishAuditInput = Omit<RecordAuditActionInput, 'actorId' | 'actorType' | 'requestId'> & Partial<Pick<RecordAuditActionInput, 'actorId' | 'actorType' | 'requestId'>>;
9
+ export declare class AuditPublisher {
10
+ private readonly mq;
11
+ private readonly logger;
12
+ constructor(mq: RabbitMQClient, logger: ILogger);
13
+ publish(input: PublishAuditInput): Promise<void>;
14
+ }
@@ -0,0 +1,49 @@
1
+ "use strict";
2
+ /**
3
+ * @file audit.publisher.ts
4
+ * @description Publishes audit events with automatic request context enrichment.
5
+ */
6
+ Object.defineProperty(exports, "__esModule", { value: true });
7
+ exports.AuditPublisher = void 0;
8
+ const dtos_1 = require("../dtos");
9
+ const request_context_storage_1 = require("../context/request-context.storage");
10
+ class AuditPublisher {
11
+ constructor(mq, logger) {
12
+ this.mq = mq;
13
+ this.logger = logger;
14
+ }
15
+ async publish(input) {
16
+ const ctx = request_context_storage_1.RequestContextProvider.get();
17
+ let actorType = dtos_1.AuditActorType.SYSTEM;
18
+ let actorId = null;
19
+ if (ctx?.actor) {
20
+ switch (ctx.actor.type) {
21
+ case 'user':
22
+ actorType = dtos_1.AuditActorType.USER;
23
+ actorId = ctx.actor.accountId;
24
+ break;
25
+ case 'machine':
26
+ actorType = dtos_1.AuditActorType.SERVICE;
27
+ actorId = ctx.actor.serviceId;
28
+ break;
29
+ }
30
+ }
31
+ const payload = {
32
+ severity: dtos_1.AuditSeverity.INFO,
33
+ timestamp: new Date().toISOString(),
34
+ actorType,
35
+ actorId,
36
+ requestId: ctx?.requestId ?? null,
37
+ ipAddress: ctx?.ipAddress ?? null,
38
+ userAgent: ctx?.userAgent ?? null,
39
+ ...input,
40
+ };
41
+ try {
42
+ await this.mq.publishEvent(`audit.${input.action}`, payload);
43
+ }
44
+ catch (err) {
45
+ this.logger.error({ err, action: input.action }, 'Failed to publish audit event');
46
+ }
47
+ }
48
+ }
49
+ exports.AuditPublisher = AuditPublisher;
@@ -0,0 +1,2 @@
1
+ export * from './rabbitmq.client';
2
+ export * from './audit.publisher';
@@ -14,6 +14,5 @@ var __exportStar = (this && this.__exportStar) || function(m, exports) {
14
14
  for (var p in m) if (p !== "default" && !Object.prototype.hasOwnProperty.call(exports, p)) __createBinding(exports, m, p);
15
15
  };
16
16
  Object.defineProperty(exports, "__esModule", { value: true });
17
- __exportStar(require("./user-service.dtos"), exports);
18
- __exportStar(require("./auth-service.dtos"), exports);
19
- __exportStar(require("./response.dtos"), exports);
17
+ __exportStar(require("./rabbitmq.client"), exports);
18
+ __exportStar(require("./audit.publisher"), exports);
@@ -0,0 +1,15 @@
1
+ import { ILogger } from '../utils/logger.util';
2
+ export declare class RabbitMQClient {
3
+ private connection;
4
+ private channel;
5
+ private url;
6
+ private exchangeName;
7
+ private logger;
8
+ constructor(url: string, logger?: ILogger);
9
+ connect(): Promise<void>;
10
+ publishEvent(routingKey: string, payload: unknown): Promise<void>;
11
+ subscribe(queueName: string, routingKey: string, handler: (payload: unknown) => Promise<void>, options?: {
12
+ prefetch?: number;
13
+ }): Promise<void>;
14
+ close(): Promise<void>;
15
+ }
@@ -0,0 +1,133 @@
1
+ "use strict";
2
+ var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
3
+ if (k2 === undefined) k2 = k;
4
+ var desc = Object.getOwnPropertyDescriptor(m, k);
5
+ if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
6
+ desc = { enumerable: true, get: function() { return m[k]; } };
7
+ }
8
+ Object.defineProperty(o, k2, desc);
9
+ }) : (function(o, m, k, k2) {
10
+ if (k2 === undefined) k2 = k;
11
+ o[k2] = m[k];
12
+ }));
13
+ var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
14
+ Object.defineProperty(o, "default", { enumerable: true, value: v });
15
+ }) : function(o, v) {
16
+ o["default"] = v;
17
+ });
18
+ var __importStar = (this && this.__importStar) || (function () {
19
+ var ownKeys = function(o) {
20
+ ownKeys = Object.getOwnPropertyNames || function (o) {
21
+ var ar = [];
22
+ for (var k in o) if (Object.prototype.hasOwnProperty.call(o, k)) ar[ar.length] = k;
23
+ return ar;
24
+ };
25
+ return ownKeys(o);
26
+ };
27
+ return function (mod) {
28
+ if (mod && mod.__esModule) return mod;
29
+ var result = {};
30
+ if (mod != null) for (var k = ownKeys(mod), i = 0; i < k.length; i++) if (k[i] !== "default") __createBinding(result, mod, k[i]);
31
+ __setModuleDefault(result, mod);
32
+ return result;
33
+ };
34
+ })();
35
+ Object.defineProperty(exports, "__esModule", { value: true });
36
+ exports.RabbitMQClient = void 0;
37
+ const amqplib = __importStar(require("amqplib"));
38
+ const logger_util_1 = require("../utils/logger.util");
39
+ class RabbitMQClient {
40
+ constructor(url, logger = logger_util_1.consoleLogger) {
41
+ this.connection = null;
42
+ this.channel = null;
43
+ this.exchangeName = 'discovercloud.topic';
44
+ this.url = url;
45
+ this.logger = logger;
46
+ }
47
+ async connect() {
48
+ if (this.connection && this.channel) {
49
+ return;
50
+ }
51
+ try {
52
+ this.connection = await amqplib.connect(this.url);
53
+ this.connection.on('error', (err) => {
54
+ this.logger.error(err, 'RabbitMQ Connection Error');
55
+ });
56
+ this.connection.on('close', () => {
57
+ this.logger.warn('RabbitMQ Connection Closed. Reconnecting...');
58
+ this.connection = null;
59
+ this.channel = null;
60
+ setTimeout(() => this.connect(), 5000);
61
+ });
62
+ this.channel = await this.connection.createChannel();
63
+ // Ensure the main topic exchange exists
64
+ await this.channel.assertExchange(this.exchangeName, 'topic', {
65
+ durable: true,
66
+ });
67
+ this.logger.info('✅ Connected to RabbitMQ');
68
+ }
69
+ catch (error) {
70
+ this.logger.error(error, '❌ Failed to connect to RabbitMQ');
71
+ setTimeout(() => this.connect(), 5000);
72
+ }
73
+ }
74
+ async publishEvent(routingKey, payload) {
75
+ if (!this.channel) {
76
+ throw new Error('RabbitMQ Channel not initialized');
77
+ }
78
+ const buffer = Buffer.from(JSON.stringify(payload));
79
+ this.channel.publish(this.exchangeName, routingKey, buffer, {
80
+ persistent: true,
81
+ contentType: 'application/json',
82
+ timestamp: Date.now(),
83
+ });
84
+ }
85
+ async subscribe(queueName, routingKey, handler, options = {}) {
86
+ if (!this.channel) {
87
+ throw new Error('RabbitMQ Channel not initialized');
88
+ }
89
+ if (options.prefetch) {
90
+ await this.channel.prefetch(options.prefetch);
91
+ }
92
+ // Dead Letter configuration
93
+ const dlxName = `${this.exchangeName}.dlx`;
94
+ const dlqName = `${queueName}.dlq`;
95
+ await this.channel.assertExchange(dlxName, 'topic', { durable: true });
96
+ await this.channel.assertQueue(dlqName, { durable: true });
97
+ await this.channel.bindQueue(dlqName, dlxName, routingKey);
98
+ // Assert main queue with DLX bindings
99
+ await this.channel.assertQueue(queueName, {
100
+ durable: true,
101
+ deadLetterExchange: dlxName,
102
+ deadLetterRoutingKey: routingKey,
103
+ });
104
+ await this.channel.bindQueue(queueName, this.exchangeName, routingKey);
105
+ this.channel.consume(queueName, async (msg) => {
106
+ if (!msg)
107
+ return;
108
+ try {
109
+ const payload = JSON.parse(msg.content.toString());
110
+ await handler(payload);
111
+ // Fix: Use optional chaining instead of non-null assertion (!)
112
+ this.channel?.ack(msg);
113
+ }
114
+ catch (error) {
115
+ this.logger.error(error, `Error processing message from ${queueName}`);
116
+ // Fix: Use optional chaining instead of non-null assertion (!)
117
+ // Nack the message and don't requeue, so it goes to the DLX
118
+ this.channel?.nack(msg, false, false);
119
+ }
120
+ });
121
+ this.logger.info(`📡 Subscribed to queue [${queueName}] with routing key [${routingKey}]`);
122
+ }
123
+ async close() {
124
+ if (this.channel) {
125
+ await this.channel.close();
126
+ }
127
+ if (this.connection) {
128
+ await this.connection.close();
129
+ }
130
+ this.logger.info('RabbitMQ connection closed.');
131
+ }
132
+ }
133
+ exports.RabbitMQClient = RabbitMQClient;
@@ -1,30 +1,10 @@
1
- import { Request, Response, NextFunction } from "express";
2
1
  /**
3
- * GLOBAL ERROR HANDLER (@discover-cloud/shared)
4
- * ────────────────────────────────────────────────
5
- * Centralised Express error handler. Register as the last middleware in
6
- * every service's app.ts:
7
- *
8
- * app.use(GlobalErrorHandler.handle);
9
- *
10
- * Handles in order:
11
- * 1. ZodError → 400 VALIDATION_ERROR with flattened field errors
12
- * 2. AppError → mapped statusCode + code (details forwarded to client)
13
- * 3. Unknown → 500 INTERNAL_SERVER_ERROR (internals never leaked)
14
- *
15
- * Logging strategy:
16
- * Development — full error object printed for stack traces during development.
17
- * Production — message only for AppError/Error (no stack, no details in logs).
18
- * Unknown errors log the raw value at warn level.
19
- * In production, a structured logger (Pino) replaces console.*
20
- * in each service — these console calls are captured by stdout.
21
- *
22
- * Security:
23
- * Unknown (unexpected) errors never expose internals to the client.
24
- * AppError.details IS forwarded — callers that set details on an AppError
25
- * are intentionally choosing to surface that context (e.g. validation hints).
26
- * Never put secrets, stack traces, or raw DB errors in AppError.details.
2
+ * @file error-handler.middleware.ts
3
+ * @description Central Express error handler. Register as the final middleware.
27
4
  */
28
- export declare class GlobalErrorHandler {
29
- static handle(err: unknown, req: Request, res: Response, _next: NextFunction): void;
30
- }
5
+ import { ErrorRequestHandler } from 'express';
6
+ import { ILogger } from '../utils';
7
+ /**
8
+ * Creates global Express error middleware.
9
+ */
10
+ export declare function createGlobalErrorHandler(logger?: ILogger): ErrorRequestHandler;
@@ -1,70 +1,79 @@
1
1
  "use strict";
2
+ /**
3
+ * @file error-handler.middleware.ts
4
+ * @description Central Express error handler. Register as the final middleware.
5
+ */
2
6
  Object.defineProperty(exports, "__esModule", { value: true });
3
- exports.GlobalErrorHandler = void 0;
7
+ exports.createGlobalErrorHandler = createGlobalErrorHandler;
4
8
  const zod_1 = require("zod");
5
9
  const errors_1 = require("../errors");
6
10
  const utils_1 = require("../utils");
7
11
  /**
8
- * GLOBAL ERROR HANDLER (@discover-cloud/shared)
9
- * ────────────────────────────────────────────────
10
- * Centralised Express error handler. Register as the last middleware in
11
- * every service's app.ts:
12
- *
13
- * app.use(GlobalErrorHandler.handle);
14
- *
15
- * Handles in order:
16
- * 1. ZodError → 400 VALIDATION_ERROR with flattened field errors
17
- * 2. AppError → mapped statusCode + code (details forwarded to client)
18
- * 3. Unknown → 500 INTERNAL_SERVER_ERROR (internals never leaked)
19
- *
20
- * Logging strategy:
21
- * Development — full error object printed for stack traces during development.
22
- * Production — message only for AppError/Error (no stack, no details in logs).
23
- * Unknown errors log the raw value at warn level.
24
- * In production, a structured logger (Pino) replaces console.*
25
- * in each service — these console calls are captured by stdout.
26
- *
27
- * Security:
28
- * Unknown (unexpected) errors never expose internals to the client.
29
- * AppError.details IS forwarded — callers that set details on an AppError
30
- * are intentionally choosing to surface that context (e.g. validation hints).
31
- * Never put secrets, stack traces, or raw DB errors in AppError.details.
12
+ * Detects malformed JSON thrown by express.json().
32
13
  */
33
- class GlobalErrorHandler {
34
- static handle(err, req, res, _next) {
35
- // ── Logging ──────────────────────────────────────────────────────────
36
- if (process.env["NODE_ENV"] !== "production") {
37
- // Full object in development — stack traces + details are useful locally.
38
- console.error(`[req ${req.id}]`, err);
39
- }
40
- else if (err instanceof Error) {
41
- // Production: message only — no stack, no details, no internal paths.
42
- console.error(`[req ${req.id}] ${err.name}: ${err.message}`);
43
- }
44
- else {
45
- // Non-Error throw (string, object, etc.) — log at warn; this should not happen.
46
- console.warn(`[req ${req.id}] Non-Error thrown:`, typeof err);
14
+ function isMalformedJsonError(err) {
15
+ return (err instanceof SyntaxError &&
16
+ 'status' in err &&
17
+ typeof err.status === 'number' &&
18
+ 'body' in err);
19
+ }
20
+ /**
21
+ * Converts any thrown value into Error.
22
+ */
23
+ function normalizeError(err) {
24
+ if (err instanceof Error) {
25
+ return err;
26
+ }
27
+ if (typeof err === 'string') {
28
+ return new Error(err);
29
+ }
30
+ return new Error(JSON.stringify(err));
31
+ }
32
+ /**
33
+ * Creates global Express error middleware.
34
+ */
35
+ function createGlobalErrorHandler(logger = utils_1.noopLogger) {
36
+ return (err, req, res, _next) => {
37
+ const error = normalizeError(err);
38
+ const requestId = req.requestContext?.requestId ?? req.id;
39
+ /**
40
+ * Malformed JSON
41
+ */
42
+ if (isMalformedJsonError(err) && err.status === 400) {
43
+ logger.warn({ requestId, err: error }, 'Malformed JSON payload');
44
+ (0, utils_1.failure)(res, req, 'Invalid JSON payload', 'BAD_REQUEST', 400);
45
+ return;
47
46
  }
48
- // ── 1. Zod validation errors ─────────────────────────────────────────
47
+ /**
48
+ * Validation failures
49
+ */
49
50
  if (err instanceof zod_1.ZodError) {
50
- // flatten() groups errors into fieldErrors (keyed by path) and formErrors
51
- // (top-level). This shape is stable across Zod versions and is what
52
- // clients should key on for field-level error display.
53
- (0, utils_1.failure)(res, req, "Validation failed", "VALIDATION_ERROR", 400, err.flatten());
51
+ logger.warn({ requestId, err: error }, 'Request validation failed');
52
+ (0, utils_1.failure)(res, req, 'Validation failed', 'VALIDATION_ERROR', 400, err.flatten());
54
53
  return;
55
54
  }
56
- // ── 2. Known AppError subclasses ─────────────────────────────────────
57
- // instanceof check — no duck-typing, no accidental matches.
58
- // AppError.details is caller-controlled context (e.g. validation hints)
59
- // and is intentionally forwarded to the client.
55
+ /**
56
+ * Known application errors
57
+ */
60
58
  if (err instanceof errors_1.AppError) {
59
+ const payload = {
60
+ requestId,
61
+ code: err.code,
62
+ err: error,
63
+ };
64
+ if (err.statusCode >= 500) {
65
+ logger.error(payload, err.message);
66
+ }
67
+ else {
68
+ logger.warn(payload, err.message);
69
+ }
61
70
  (0, utils_1.failure)(res, req, err.message, err.code, err.statusCode, err.details);
62
71
  return;
63
72
  }
64
- // ── 3. Unexpected errors — never leak internals ───────────────────────
65
- // Generic 500 with no details. The actual error is logged above (production:
66
- // message only; development: full object). Nothing internal reaches the client.
67
- (0, utils_1.failure)(res, req, "Internal Server Error", "INTERNAL_SERVER_ERROR", 500);
68
- }
73
+ /**
74
+ * Unknown crashes
75
+ */
76
+ logger.error({ requestId, err: error }, 'Unhandled exception');
77
+ (0, utils_1.failure)(res, req, 'Internal Server Error', 'INTERNAL_SERVER_ERROR', 500);
78
+ };
69
79
  }
70
- exports.GlobalErrorHandler = GlobalErrorHandler;
@@ -1,8 +1,10 @@
1
- export * from "./error-handler.middleware";
2
- export * from "./validate.middleware";
3
- export * from "./request-id.middleware";
4
- export * from "./authorize.middleware";
5
- export * from "./validated-merge.middleware";
6
- export * from "./require-internal.middleware";
7
- export * from "./require-auth.middleware";
8
- export * from "./require-human.middleware";
1
+ export * from './error-handler.middleware';
2
+ export * from './validate.middleware';
3
+ export * from './request-context.middleware';
4
+ export * from './require-machine.middleware';
5
+ export * from './require-auth.middleware';
6
+ export * from './require-user.middleware';
7
+ export * from './require-org-permission.middleware';
8
+ export * from './require-workspace-permission.middleware';
9
+ export * from './require-platform-permission.middleware';
10
+ export * from './require-org-permission-from-body.middleware';
@@ -16,9 +16,11 @@ var __exportStar = (this && this.__exportStar) || function(m, exports) {
16
16
  Object.defineProperty(exports, "__esModule", { value: true });
17
17
  __exportStar(require("./error-handler.middleware"), exports);
18
18
  __exportStar(require("./validate.middleware"), exports);
19
- __exportStar(require("./request-id.middleware"), exports);
20
- __exportStar(require("./authorize.middleware"), exports);
21
- __exportStar(require("./validated-merge.middleware"), exports);
22
- __exportStar(require("./require-internal.middleware"), exports);
19
+ __exportStar(require("./request-context.middleware"), exports);
20
+ __exportStar(require("./require-machine.middleware"), exports);
23
21
  __exportStar(require("./require-auth.middleware"), exports);
24
- __exportStar(require("./require-human.middleware"), exports);
22
+ __exportStar(require("./require-user.middleware"), exports);
23
+ __exportStar(require("./require-org-permission.middleware"), exports);
24
+ __exportStar(require("./require-workspace-permission.middleware"), exports);
25
+ __exportStar(require("./require-platform-permission.middleware"), exports);
26
+ __exportStar(require("./require-org-permission-from-body.middleware"), exports);
@@ -0,0 +1,6 @@
1
+ /**
2
+ * @file request-context.middleware.ts
3
+ * @description Express middleware to assign request tracing and audit metadata context.
4
+ */
5
+ import { Request, Response, NextFunction } from 'express';
6
+ export declare const requestContext: (req: Request, res: Response, next: NextFunction) => void;
@@ -0,0 +1,48 @@
1
+ "use strict";
2
+ /**
3
+ * @file request-context.middleware.ts
4
+ * @description Express middleware to assign request tracing and audit metadata context.
5
+ */
6
+ Object.defineProperty(exports, "__esModule", { value: true });
7
+ exports.requestContext = void 0;
8
+ const crypto_1 = require("crypto");
9
+ const request_context_storage_1 = require("../context/request-context.storage");
10
+ function normalizeIp(ip) {
11
+ if (!ip)
12
+ return null;
13
+ if (ip === '::1') {
14
+ return '127.0.0.1';
15
+ }
16
+ if (ip.startsWith('::ffff:')) {
17
+ return ip.substring(7);
18
+ }
19
+ return ip;
20
+ }
21
+ function resolveClientIp(req) {
22
+ const forwarded = req.headers['x-forwarded-for'];
23
+ if (forwarded) {
24
+ const value = Array.isArray(forwarded) ? forwarded[0] : forwarded;
25
+ return normalizeIp(value.split(',')[0].trim());
26
+ }
27
+ const realIp = req.headers['x-real-ip'];
28
+ if (realIp) {
29
+ return normalizeIp(Array.isArray(realIp) ? realIp[0] : realIp);
30
+ }
31
+ return normalizeIp(req.socket.remoteAddress ?? null);
32
+ }
33
+ const requestContext = (req, res, next) => {
34
+ const upstream = req.headers['x-request-id'];
35
+ const requestId = Array.isArray(upstream) ? upstream[0] : (upstream ?? (0, crypto_1.randomUUID)());
36
+ // Explicitly type this as AuditRequestContext so TS knows it's mutable
37
+ // and allows the `.actor` property to be assigned by the auth middlewares later.
38
+ const context = {
39
+ requestId,
40
+ ipAddress: resolveClientIp(req),
41
+ userAgent: req.headers['user-agent'] ?? null,
42
+ };
43
+ req.id = requestId;
44
+ req.requestContext = context;
45
+ res.setHeader('x-request-id', requestId);
46
+ request_context_storage_1.requestContextStorage.run(context, () => next());
47
+ };
48
+ exports.requestContext = requestContext;