@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.
- package/dist/authorization/index.d.ts +2 -2
- package/dist/authorization/index.js +1 -1
- package/dist/authorization/permission-cache.service.d.ts +104 -16
- package/dist/authorization/permission-cache.service.js +177 -148
- package/dist/authorization/permission-checker.d.ts +30 -0
- package/dist/authorization/permission-checker.js +40 -0
- package/dist/context/index.d.ts +1 -1
- package/dist/context/index.js +1 -1
- package/dist/context/request-context.storage.d.ts +21 -0
- package/dist/context/request-context.storage.js +37 -0
- package/dist/contracts/auth-service/account.dto.d.ts +30 -0
- package/dist/contracts/auth-service/account.dto.js +7 -0
- package/dist/contracts/auth-service/auth.dto.d.ts +21 -0
- package/dist/contracts/auth-service/auth.dto.js +10 -0
- package/dist/contracts/index.d.ts +1 -0
- package/dist/{security → contracts}/index.js +1 -1
- package/dist/contracts/visibility.d.ts +63 -0
- package/dist/contracts/visibility.js +10 -0
- package/dist/dtos/audit-service.types.d.ts +42 -0
- package/dist/dtos/audit-service.types.js +31 -0
- package/dist/dtos/auth-service.dto.d.ts +87 -26
- package/dist/dtos/auth-service.dto.js +4 -0
- package/dist/dtos/cloud-service.dto.d.ts +57 -27
- package/dist/dtos/cloud-service.dto.js +5 -0
- package/dist/dtos/index.d.ts +6 -5
- package/dist/dtos/index.js +1 -0
- package/dist/dtos/insights-service.dto.d.ts +184 -32
- package/dist/dtos/insights-service.dto.js +8 -0
- package/dist/dtos/response.dto.d.ts +12 -83
- package/dist/dtos/response.dto.js +2 -21
- package/dist/dtos/user-service.dto.d.ts +163 -13
- package/dist/enums/domain.enums.d.ts +272 -75
- package/dist/enums/domain.enums.js +329 -102
- package/dist/enums/index.d.ts +2 -2
- package/dist/enums/permissions.enums.d.ts +142 -80
- package/dist/enums/permissions.enums.js +174 -133
- package/dist/errors/app-error.d.ts +13 -18
- package/dist/errors/app-error.js +15 -21
- package/dist/errors/http-errors.d.ts +106 -18
- package/dist/errors/http-errors.js +136 -51
- package/dist/errors/index.d.ts +2 -2
- package/dist/http/index.d.ts +2 -1
- package/dist/http/index.js +1 -0
- package/dist/http/request-context.d.ts +8 -0
- package/dist/http/request-context.js +37 -0
- package/dist/http/service-client.d.ts +93 -40
- package/dist/http/service-client.js +104 -55
- package/dist/index.d.ts +11 -9
- package/dist/index.js +2 -0
- package/dist/jwt/index.d.ts +3 -2
- package/dist/jwt/index.js +2 -1
- package/dist/jwt/machine-jwt-verifier.d.ts +21 -0
- package/dist/jwt/machine-jwt-verifier.js +83 -0
- package/dist/jwt/machine-token-client.d.ts +36 -5
- package/dist/jwt/machine-token-client.js +61 -27
- package/dist/jwt/user-jwt-verifier.d.ts +31 -0
- package/dist/jwt/user-jwt-verifier.js +102 -0
- package/dist/messaging/audit.publisher.d.ts +14 -0
- package/dist/messaging/audit.publisher.js +49 -0
- package/dist/messaging/index.d.ts +2 -0
- package/dist/{dto → messaging}/index.js +2 -3
- package/dist/messaging/rabbitmq.client.d.ts +15 -0
- package/dist/messaging/rabbitmq.client.js +133 -0
- package/dist/middleware/error-handler.middleware.d.ts +8 -28
- package/dist/middleware/error-handler.middleware.js +63 -54
- package/dist/middleware/index.d.ts +10 -8
- package/dist/middleware/index.js +7 -5
- package/dist/middleware/request-context.middleware.d.ts +6 -0
- package/dist/middleware/request-context.middleware.js +48 -0
- package/dist/middleware/require-auth.middleware.d.ts +7 -76
- package/dist/middleware/require-auth.middleware.js +29 -117
- package/dist/middleware/require-machine.middleware.d.ts +13 -0
- package/dist/middleware/require-machine.middleware.js +128 -0
- package/dist/middleware/require-org-permission-from-body.middleware.d.ts +10 -0
- package/dist/middleware/require-org-permission-from-body.middleware.js +23 -0
- package/dist/middleware/require-org-permission.middleware.d.ts +19 -0
- package/dist/middleware/require-org-permission.middleware.js +115 -0
- package/dist/middleware/require-platform-permission.middleware.d.ts +19 -0
- package/dist/middleware/require-platform-permission.middleware.js +91 -0
- package/dist/middleware/require-user.middleware.d.ts +14 -0
- package/dist/middleware/require-user.middleware.js +30 -0
- package/dist/middleware/require-workspace-permission.middleware.d.ts +20 -0
- package/dist/middleware/require-workspace-permission.middleware.js +114 -0
- package/dist/middleware/validate.middleware.d.ts +31 -31
- package/dist/middleware/validate.middleware.js +31 -33
- package/dist/types/audit.types.d.ts +39 -0
- package/dist/types/audit.types.js +30 -0
- package/dist/types/express.types.d.ts +92 -124
- package/dist/types/express.types.js +30 -49
- package/dist/types/index.d.ts +1 -1
- package/dist/utils/date.util.d.ts +6 -0
- package/dist/utils/date.util.js +11 -0
- package/dist/utils/env.util.d.ts +6 -0
- package/dist/utils/env.util.js +19 -0
- package/dist/utils/index.d.ts +5 -4
- package/dist/utils/index.js +5 -4
- package/dist/utils/logger.util.d.ts +31 -0
- package/dist/utils/logger.util.js +91 -0
- package/dist/utils/pagination.util.d.ts +6 -0
- package/dist/utils/pagination.util.js +20 -0
- package/dist/utils/request-context.als.d.ts +10 -0
- package/dist/utils/request-context.als.js +5 -0
- package/dist/utils/response.util.d.ts +27 -0
- package/dist/utils/response.util.js +56 -0
- package/dist/utils/slug.util.d.ts +9 -0
- package/dist/utils/slug.util.js +32 -0
- package/dist/utils/url-safety.util.d.ts +6 -0
- package/dist/utils/url-safety.util.js +75 -0
- package/package.json +3 -1
- package/dist/authorization/permissions.d.ts +0 -78
- package/dist/authorization/permissions.js +0 -174
- package/dist/context/access-context.d.ts +0 -10
- package/dist/context/access-context.js +0 -2
- package/dist/dto/auth-service.dtos.d.ts +0 -44
- package/dist/dto/auth-service.dtos.js +0 -2
- package/dist/dto/index.d.ts +0 -3
- package/dist/dto/response.dtos.d.ts +0 -55
- package/dist/dto/response.dtos.js +0 -6
- package/dist/dto/user-service.dtos.d.ts +0 -50
- package/dist/dto/user-service.dtos.js +0 -2
- package/dist/enums/auth-service.enums.d.ts +0 -12
- package/dist/enums/auth-service.enums.js +0 -17
- package/dist/enums/permissions.types.d.ts +0 -12
- package/dist/enums/permissions.types.js +0 -17
- package/dist/enums/user-service.enums.d.ts +0 -32
- package/dist/enums/user-service.enums.js +0 -41
- package/dist/internal/index.d.ts +0 -4
- package/dist/internal/index.js +0 -20
- package/dist/internal/internal-jwt.service.d.ts +0 -13
- package/dist/internal/internal-jwt.service.js +0 -88
- package/dist/internal/internal-jwt.types.d.ts +0 -7
- package/dist/internal/internal-jwt.types.js +0 -2
- package/dist/internal/internal-key-manager.d.ts +0 -16
- package/dist/internal/internal-key-manager.js +0 -67
- package/dist/internal/registry.d.ts +0 -8
- package/dist/internal/registry.js +0 -34
- package/dist/internal/service-client.d.ts +0 -9
- package/dist/internal/service-client.js +0 -94
- package/dist/jwt/internal-jwt-verifier.d.ts +0 -41
- package/dist/jwt/internal-jwt-verifier.js +0 -185
- package/dist/jwt/jwt-verifier.d.ts +0 -9
- package/dist/jwt/jwt-verifier.js +0 -36
- package/dist/jwt/service-client.d.ts +0 -7
- package/dist/jwt/service-client.js +0 -87
- package/dist/middleware/authorize.d.ts +0 -3
- package/dist/middleware/authorize.js +0 -24
- package/dist/middleware/authorize.middleware.d.ts +0 -54
- package/dist/middleware/authorize.middleware.js +0 -104
- package/dist/middleware/error-handler.d.ts +0 -4
- package/dist/middleware/error-handler.js +0 -23
- package/dist/middleware/request-id.d.ts +0 -2
- package/dist/middleware/request-id.js +0 -9
- package/dist/middleware/request-id.middleware.d.ts +0 -22
- package/dist/middleware/request-id.middleware.js +0 -34
- package/dist/middleware/require-auth.d.ts +0 -10
- package/dist/middleware/require-auth.js +0 -34
- package/dist/middleware/require-human.middleware.d.ts +0 -2
- package/dist/middleware/require-human.middleware.js +0 -18
- package/dist/middleware/require-internal.middleware.d.ts +0 -18
- package/dist/middleware/require-internal.middleware.js +0 -183
- package/dist/middleware/validate.d.ts +0 -5
- package/dist/middleware/validate.js +0 -18
- package/dist/middleware/validated-merge.middleware.d.ts +0 -20
- package/dist/middleware/validated-merge.middleware.js +0 -33
- package/dist/middleware/verify-internal-jwt.d.ts +0 -7
- package/dist/middleware/verify-internal-jwt.js +0 -25
- package/dist/security/guard.d.ts +0 -10
- package/dist/security/guard.js +0 -40
- package/dist/security/index.d.ts +0 -1
- package/dist/types/express.d.ts +0 -22
- package/dist/types/express.js +0 -3
- package/dist/utils/date.utils.d.ts +0 -25
- package/dist/utils/date.utils.js +0 -30
- package/dist/utils/env.d.ts +0 -46
- package/dist/utils/env.js +0 -61
- package/dist/utils/env.utils.d.ts +0 -46
- package/dist/utils/env.utils.js +0 -61
- package/dist/utils/logger.utils.d.ts +0 -66
- package/dist/utils/logger.utils.js +0 -97
- package/dist/utils/response.d.ts +0 -4
- package/dist/utils/response.js +0 -35
- package/dist/utils/response.utils.d.ts +0 -54
- 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;
|
|
@@ -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("./
|
|
18
|
-
__exportStar(require("./
|
|
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
|
-
*
|
|
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
|
-
|
|
29
|
-
|
|
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.
|
|
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
|
-
*
|
|
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
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
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
|
-
|
|
47
|
+
/**
|
|
48
|
+
* Validation failures
|
|
49
|
+
*/
|
|
49
50
|
if (err instanceof zod_1.ZodError) {
|
|
50
|
-
|
|
51
|
-
|
|
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
|
-
|
|
57
|
-
|
|
58
|
-
|
|
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
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
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
|
|
2
|
-
export * from
|
|
3
|
-
export * from
|
|
4
|
-
export * from
|
|
5
|
-
export * from
|
|
6
|
-
export * from
|
|
7
|
-
export * from
|
|
8
|
-
export * from
|
|
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';
|
package/dist/middleware/index.js
CHANGED
|
@@ -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-
|
|
20
|
-
__exportStar(require("./
|
|
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-
|
|
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;
|