@discover-cloud/shared 1.3.0 → 1.5.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/authorization/index.d.ts +2 -2
- package/dist/authorization/permission-cache.service.js +27 -11
- package/dist/context/index.d.ts +1 -0
- package/dist/context/index.js +17 -0
- 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 +1 -1
- package/dist/contracts/index.d.ts +1 -0
- package/dist/contracts/index.js +17 -0
- 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 +16 -1
- package/dist/dtos/cloud-service.dto.d.ts +9 -3
- package/dist/dtos/index.d.ts +6 -5
- package/dist/dtos/index.js +1 -0
- package/dist/dtos/insights-service.dto.d.ts +174 -13
- package/dist/dtos/response.dto.d.ts +6 -0
- package/dist/dtos/user-service.dto.d.ts +52 -7
- package/dist/enums/domain.enums.d.ts +52 -26
- package/dist/enums/domain.enums.js +64 -40
- package/dist/enums/index.d.ts +2 -2
- package/dist/errors/app-error.js +1 -1
- package/dist/errors/http-errors.d.ts +1 -1
- package/dist/errors/http-errors.js +26 -26
- package/dist/errors/index.d.ts +2 -2
- package/dist/http/index.d.ts +2 -2
- package/dist/http/request-context.d.ts +2 -2
- package/dist/http/request-context.js +5 -5
- package/dist/http/service-client.d.ts +2 -2
- package/dist/http/service-client.js +2 -2
- package/dist/index.d.ts +11 -10
- package/dist/index.js +1 -0
- package/dist/jwt/index.d.ts +3 -3
- package/dist/jwt/machine-jwt-verifier.d.ts +1 -1
- package/dist/jwt/machine-jwt-verifier.js +13 -14
- package/dist/jwt/machine-token-client.d.ts +1 -1
- package/dist/jwt/machine-token-client.js +8 -8
- package/dist/jwt/user-jwt-verifier.d.ts +1 -1
- package/dist/jwt/user-jwt-verifier.js +17 -16
- package/dist/messaging/audit.publisher.d.ts +14 -0
- package/dist/messaging/audit.publisher.js +49 -0
- package/dist/messaging/index.d.ts +1 -0
- package/dist/messaging/index.js +1 -0
- package/dist/messaging/rabbitmq.client.d.ts +17 -5
- package/dist/messaging/rabbitmq.client.js +83 -36
- package/dist/middleware/error-handler.middleware.d.ts +2 -2
- package/dist/middleware/error-handler.middleware.js +10 -10
- package/dist/middleware/index.d.ts +10 -10
- package/dist/middleware/request-context.middleware.d.ts +1 -12
- package/dist/middleware/request-context.middleware.js +17 -42
- package/dist/middleware/require-auth.middleware.d.ts +3 -19
- package/dist/middleware/require-auth.middleware.js +17 -26
- package/dist/middleware/require-machine.middleware.d.ts +2 -27
- package/dist/middleware/require-machine.middleware.js +30 -25
- package/dist/middleware/require-org-permission-from-body.middleware.js +1 -2
- package/dist/middleware/require-org-permission.middleware.d.ts +3 -3
- package/dist/middleware/require-org-permission.middleware.js +28 -28
- package/dist/middleware/require-platform-permission.middleware.d.ts +3 -3
- package/dist/middleware/require-platform-permission.middleware.js +10 -10
- package/dist/middleware/require-user.middleware.d.ts +1 -1
- package/dist/middleware/require-user.middleware.js +2 -2
- package/dist/middleware/require-workspace-permission.middleware.d.ts +3 -3
- package/dist/middleware/require-workspace-permission.middleware.js +25 -25
- package/dist/middleware/validate.middleware.d.ts +3 -3
- package/dist/middleware/validate.middleware.js +1 -1
- package/dist/types/audit.types.d.ts +39 -0
- package/dist/types/audit.types.js +30 -0
- package/dist/types/express.types.d.ts +14 -7
- package/dist/types/express.types.js +10 -10
- package/dist/types/index.d.ts +1 -1
- package/dist/utils/env.util.js +1 -1
- package/dist/utils/index.d.ts +5 -5
- package/dist/utils/logger.util.d.ts +5 -0
- package/dist/utils/logger.util.js +46 -8
- 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 +1 -1
- package/dist/utils/url-safety.util.js +4 -1
- package/package.json +1 -1
|
@@ -35,37 +35,66 @@ var __importStar = (this && this.__importStar) || (function () {
|
|
|
35
35
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
36
36
|
exports.RabbitMQClient = void 0;
|
|
37
37
|
const amqplib = __importStar(require("amqplib"));
|
|
38
|
+
const logger_util_1 = require("../utils/logger.util");
|
|
38
39
|
class RabbitMQClient {
|
|
39
|
-
constructor(url) {
|
|
40
|
+
constructor(url, logger = logger_util_1.consoleLogger) {
|
|
40
41
|
this.connection = null;
|
|
41
42
|
this.channel = null;
|
|
42
43
|
this.exchangeName = 'discovercloud.topic';
|
|
44
|
+
this.reconnecting = false;
|
|
43
45
|
this.url = url;
|
|
46
|
+
this.logger = logger;
|
|
44
47
|
}
|
|
48
|
+
/**
|
|
49
|
+
* Establishes a connection to RabbitMQ.
|
|
50
|
+
* Blocks until a connection is successfully established.
|
|
51
|
+
*/
|
|
45
52
|
async connect() {
|
|
46
53
|
if (this.connection && this.channel) {
|
|
47
54
|
return;
|
|
48
55
|
}
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
this.connection
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
56
|
+
while (!this.channel) {
|
|
57
|
+
try {
|
|
58
|
+
this.logger.info('Connecting to RabbitMQ...');
|
|
59
|
+
this.connection = await amqplib.connect(this.url);
|
|
60
|
+
this.connection.on('error', (err) => {
|
|
61
|
+
this.logger.error(err, 'RabbitMQ Connection Error');
|
|
62
|
+
});
|
|
63
|
+
this.connection.on('close', () => {
|
|
64
|
+
this.logger.warn('RabbitMQ Connection Closed');
|
|
65
|
+
this.connection = null;
|
|
66
|
+
this.channel = null;
|
|
67
|
+
if (!this.reconnecting) {
|
|
68
|
+
void this.reconnect();
|
|
69
|
+
}
|
|
70
|
+
});
|
|
71
|
+
this.channel = await this.connection.createChannel();
|
|
72
|
+
await this.channel.assertExchange(this.exchangeName, 'topic', {
|
|
73
|
+
durable: true,
|
|
74
|
+
});
|
|
75
|
+
this.logger.info('✅ RabbitMQ connected');
|
|
76
|
+
return;
|
|
77
|
+
}
|
|
78
|
+
catch (err) {
|
|
79
|
+
this.logger.error(err, 'RabbitMQ unavailable. Retrying in 5 seconds...');
|
|
80
|
+
await this.delay(5000);
|
|
81
|
+
}
|
|
64
82
|
}
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
83
|
+
}
|
|
84
|
+
/**
|
|
85
|
+
* Background reconnect loop.
|
|
86
|
+
*/
|
|
87
|
+
async reconnect() {
|
|
88
|
+
this.reconnecting = true;
|
|
89
|
+
while (!this.channel) {
|
|
90
|
+
try {
|
|
91
|
+
await this.connect();
|
|
92
|
+
}
|
|
93
|
+
catch {
|
|
94
|
+
await this.delay(5000);
|
|
95
|
+
}
|
|
68
96
|
}
|
|
97
|
+
this.reconnecting = false;
|
|
69
98
|
}
|
|
70
99
|
async publishEvent(routingKey, payload) {
|
|
71
100
|
if (!this.channel) {
|
|
@@ -85,43 +114,61 @@ class RabbitMQClient {
|
|
|
85
114
|
if (options.prefetch) {
|
|
86
115
|
await this.channel.prefetch(options.prefetch);
|
|
87
116
|
}
|
|
88
|
-
// Dead Letter configuration
|
|
89
117
|
const dlxName = `${this.exchangeName}.dlx`;
|
|
90
118
|
const dlqName = `${queueName}.dlq`;
|
|
91
|
-
await this.channel.assertExchange(dlxName, 'topic', {
|
|
92
|
-
|
|
119
|
+
await this.channel.assertExchange(dlxName, 'topic', {
|
|
120
|
+
durable: true,
|
|
121
|
+
});
|
|
122
|
+
await this.channel.assertQueue(dlqName, {
|
|
123
|
+
durable: true,
|
|
124
|
+
});
|
|
93
125
|
await this.channel.bindQueue(dlqName, dlxName, routingKey);
|
|
94
|
-
// Assert main queue with DLX bindings
|
|
95
126
|
await this.channel.assertQueue(queueName, {
|
|
96
127
|
durable: true,
|
|
97
128
|
deadLetterExchange: dlxName,
|
|
98
129
|
deadLetterRoutingKey: routingKey,
|
|
99
130
|
});
|
|
100
131
|
await this.channel.bindQueue(queueName, this.exchangeName, routingKey);
|
|
101
|
-
this.channel.consume(queueName, async (msg) => {
|
|
102
|
-
if (!msg)
|
|
132
|
+
await this.channel.consume(queueName, async (msg) => {
|
|
133
|
+
if (!msg) {
|
|
103
134
|
return;
|
|
135
|
+
}
|
|
104
136
|
try {
|
|
105
137
|
const payload = JSON.parse(msg.content.toString());
|
|
106
138
|
await handler(payload);
|
|
107
|
-
this.channel
|
|
139
|
+
this.channel?.ack(msg);
|
|
108
140
|
}
|
|
109
|
-
catch (
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
this.channel.nack(msg, false, false);
|
|
141
|
+
catch (err) {
|
|
142
|
+
this.logger.error(err, `Error processing ${queueName}`);
|
|
143
|
+
this.channel?.nack(msg, false, false);
|
|
113
144
|
}
|
|
114
145
|
});
|
|
115
|
-
|
|
146
|
+
this.logger.info(`📡 Subscribed to ${queueName} (${routingKey})`);
|
|
116
147
|
}
|
|
117
148
|
async close() {
|
|
118
|
-
|
|
119
|
-
|
|
149
|
+
try {
|
|
150
|
+
this.reconnecting = true; // Prevents triggering reconnect during manual close
|
|
151
|
+
if (this.connection) {
|
|
152
|
+
this.connection.removeAllListeners('close');
|
|
153
|
+
}
|
|
154
|
+
if (this.channel) {
|
|
155
|
+
await this.channel.close();
|
|
156
|
+
}
|
|
157
|
+
if (this.connection) {
|
|
158
|
+
await this.connection.close();
|
|
159
|
+
}
|
|
120
160
|
}
|
|
121
|
-
|
|
122
|
-
|
|
161
|
+
finally {
|
|
162
|
+
this.channel = null;
|
|
163
|
+
this.connection = null;
|
|
164
|
+
this.reconnecting = false;
|
|
123
165
|
}
|
|
124
|
-
|
|
166
|
+
this.logger.info('RabbitMQ disconnected');
|
|
167
|
+
}
|
|
168
|
+
delay(ms) {
|
|
169
|
+
return new Promise((resolve) => {
|
|
170
|
+
setTimeout(resolve, ms);
|
|
171
|
+
});
|
|
125
172
|
}
|
|
126
173
|
}
|
|
127
174
|
exports.RabbitMQClient = RabbitMQClient;
|
|
@@ -2,8 +2,8 @@
|
|
|
2
2
|
* @file error-handler.middleware.ts
|
|
3
3
|
* @description Central Express error handler. Register as the final middleware.
|
|
4
4
|
*/
|
|
5
|
-
import { ErrorRequestHandler } from
|
|
6
|
-
import { ILogger } from
|
|
5
|
+
import { ErrorRequestHandler } from 'express';
|
|
6
|
+
import { ILogger } from '../utils';
|
|
7
7
|
/**
|
|
8
8
|
* Creates global Express error middleware.
|
|
9
9
|
*/
|
|
@@ -13,9 +13,9 @@ const utils_1 = require("../utils");
|
|
|
13
13
|
*/
|
|
14
14
|
function isMalformedJsonError(err) {
|
|
15
15
|
return (err instanceof SyntaxError &&
|
|
16
|
-
|
|
17
|
-
typeof err.status ===
|
|
18
|
-
|
|
16
|
+
'status' in err &&
|
|
17
|
+
typeof err.status === 'number' &&
|
|
18
|
+
'body' in err);
|
|
19
19
|
}
|
|
20
20
|
/**
|
|
21
21
|
* Converts any thrown value into Error.
|
|
@@ -24,7 +24,7 @@ function normalizeError(err) {
|
|
|
24
24
|
if (err instanceof Error) {
|
|
25
25
|
return err;
|
|
26
26
|
}
|
|
27
|
-
if (typeof err ===
|
|
27
|
+
if (typeof err === 'string') {
|
|
28
28
|
return new Error(err);
|
|
29
29
|
}
|
|
30
30
|
return new Error(JSON.stringify(err));
|
|
@@ -40,16 +40,16 @@ function createGlobalErrorHandler(logger = utils_1.noopLogger) {
|
|
|
40
40
|
* Malformed JSON
|
|
41
41
|
*/
|
|
42
42
|
if (isMalformedJsonError(err) && err.status === 400) {
|
|
43
|
-
logger.warn({ requestId, err: error },
|
|
44
|
-
(0, utils_1.failure)(res, req,
|
|
43
|
+
logger.warn({ requestId, err: error }, 'Malformed JSON payload');
|
|
44
|
+
(0, utils_1.failure)(res, req, 'Invalid JSON payload', 'BAD_REQUEST', 400);
|
|
45
45
|
return;
|
|
46
46
|
}
|
|
47
47
|
/**
|
|
48
48
|
* Validation failures
|
|
49
49
|
*/
|
|
50
50
|
if (err instanceof zod_1.ZodError) {
|
|
51
|
-
logger.warn({ requestId, err: error },
|
|
52
|
-
(0, utils_1.failure)(res, req,
|
|
51
|
+
logger.warn({ requestId, err: error }, 'Request validation failed');
|
|
52
|
+
(0, utils_1.failure)(res, req, 'Validation failed', 'VALIDATION_ERROR', 400, err.flatten());
|
|
53
53
|
return;
|
|
54
54
|
}
|
|
55
55
|
/**
|
|
@@ -73,7 +73,7 @@ function createGlobalErrorHandler(logger = utils_1.noopLogger) {
|
|
|
73
73
|
/**
|
|
74
74
|
* Unknown crashes
|
|
75
75
|
*/
|
|
76
|
-
logger.error({ requestId, err: error },
|
|
77
|
-
(0, utils_1.failure)(res, req,
|
|
76
|
+
logger.error({ requestId, err: error }, 'Unhandled exception');
|
|
77
|
+
(0, utils_1.failure)(res, req, 'Internal Server Error', 'INTERNAL_SERVER_ERROR', 500);
|
|
78
78
|
};
|
|
79
79
|
}
|
|
@@ -1,10 +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
|
|
9
|
-
export * from
|
|
10
|
-
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';
|
|
@@ -2,16 +2,5 @@
|
|
|
2
2
|
* @file request-context.middleware.ts
|
|
3
3
|
* @description Express middleware to assign request tracing and audit metadata context.
|
|
4
4
|
*/
|
|
5
|
-
import { Request, Response, NextFunction } from
|
|
6
|
-
/**
|
|
7
|
-
* Attaches request metadata used for:
|
|
8
|
-
* - distributed tracing
|
|
9
|
-
* - audit logs
|
|
10
|
-
* - debugging correlation
|
|
11
|
-
*
|
|
12
|
-
* @param {Request} req - Express request object.
|
|
13
|
-
* @param {Response} res - Express response object.
|
|
14
|
-
* @param {NextFunction} next - Express next middleware function.
|
|
15
|
-
* @returns {void}
|
|
16
|
-
*/
|
|
5
|
+
import { Request, Response, NextFunction } from 'express';
|
|
17
6
|
export declare const requestContext: (req: Request, res: Response, next: NextFunction) => void;
|
|
@@ -6,68 +6,43 @@
|
|
|
6
6
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
7
7
|
exports.requestContext = void 0;
|
|
8
8
|
const crypto_1 = require("crypto");
|
|
9
|
-
|
|
10
|
-
* Normalizes IPv4-mapped IPv6 addresses.
|
|
11
|
-
*
|
|
12
|
-
* @param {string | null} ip - Raw IP.
|
|
13
|
-
* @returns {string | null} Normalized IP.
|
|
14
|
-
*/
|
|
9
|
+
const request_context_storage_1 = require("../context/request-context.storage");
|
|
15
10
|
function normalizeIp(ip) {
|
|
16
|
-
if (!ip)
|
|
11
|
+
if (!ip)
|
|
17
12
|
return null;
|
|
13
|
+
if (ip === '::1') {
|
|
14
|
+
return '127.0.0.1';
|
|
18
15
|
}
|
|
19
|
-
if (ip
|
|
20
|
-
return "127.0.0.1";
|
|
21
|
-
}
|
|
22
|
-
if (ip.startsWith("::ffff:")) {
|
|
16
|
+
if (ip.startsWith('::ffff:')) {
|
|
23
17
|
return ip.substring(7);
|
|
24
18
|
}
|
|
25
19
|
return ip;
|
|
26
20
|
}
|
|
27
|
-
/**
|
|
28
|
-
* Resolves original client IP forwarded by the API Gateway.
|
|
29
|
-
*
|
|
30
|
-
* Priority:
|
|
31
|
-
* 1. x-forwarded-for
|
|
32
|
-
* 2. x-real-ip
|
|
33
|
-
* 3. socket fallback (local development only)
|
|
34
|
-
*
|
|
35
|
-
* @param {Request} req - Express request object.
|
|
36
|
-
* @returns {string | null} Client IP address.
|
|
37
|
-
*/
|
|
38
21
|
function resolveClientIp(req) {
|
|
39
|
-
const forwarded = req.headers[
|
|
22
|
+
const forwarded = req.headers['x-forwarded-for'];
|
|
40
23
|
if (forwarded) {
|
|
41
24
|
const value = Array.isArray(forwarded) ? forwarded[0] : forwarded;
|
|
42
|
-
return normalizeIp(value.split(
|
|
25
|
+
return normalizeIp(value.split(',')[0].trim());
|
|
43
26
|
}
|
|
44
|
-
const realIp = req.headers[
|
|
27
|
+
const realIp = req.headers['x-real-ip'];
|
|
45
28
|
if (realIp) {
|
|
46
29
|
return normalizeIp(Array.isArray(realIp) ? realIp[0] : realIp);
|
|
47
30
|
}
|
|
48
31
|
return normalizeIp(req.socket.remoteAddress ?? null);
|
|
49
32
|
}
|
|
50
|
-
/**
|
|
51
|
-
* Attaches request metadata used for:
|
|
52
|
-
* - distributed tracing
|
|
53
|
-
* - audit logs
|
|
54
|
-
* - debugging correlation
|
|
55
|
-
*
|
|
56
|
-
* @param {Request} req - Express request object.
|
|
57
|
-
* @param {Response} res - Express response object.
|
|
58
|
-
* @param {NextFunction} next - Express next middleware function.
|
|
59
|
-
* @returns {void}
|
|
60
|
-
*/
|
|
61
33
|
const requestContext = (req, res, next) => {
|
|
62
|
-
const upstream = req.headers[
|
|
34
|
+
const upstream = req.headers['x-request-id'];
|
|
63
35
|
const requestId = Array.isArray(upstream) ? upstream[0] : (upstream ?? (0, crypto_1.randomUUID)());
|
|
64
|
-
|
|
65
|
-
|
|
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 = {
|
|
66
39
|
requestId,
|
|
67
40
|
ipAddress: resolveClientIp(req),
|
|
68
|
-
userAgent: req.headers[
|
|
41
|
+
userAgent: req.headers['user-agent'] ?? null,
|
|
69
42
|
};
|
|
70
|
-
|
|
71
|
-
|
|
43
|
+
req.id = requestId;
|
|
44
|
+
req.requestContext = context;
|
|
45
|
+
res.setHeader('x-request-id', requestId);
|
|
46
|
+
request_context_storage_1.requestContextStorage.run(context, () => next());
|
|
72
47
|
};
|
|
73
48
|
exports.requestContext = requestContext;
|
|
@@ -3,29 +3,13 @@
|
|
|
3
3
|
* @description Authentication boundary middleware. Verifies the internal gateway JWT,
|
|
4
4
|
* builds the auth context, and gates endpoints.
|
|
5
5
|
*/
|
|
6
|
-
import { Request, Response, NextFunction } from
|
|
7
|
-
import { ILogger } from
|
|
8
|
-
import { UserJwtVerifier } from
|
|
9
|
-
/**
|
|
10
|
-
* Middleware handling authentication validation.
|
|
11
|
-
* Extracts the JWT, verifies it via UserJwtVerifier, and attaches the AuthContext.
|
|
12
|
-
*/
|
|
6
|
+
import { Request, Response, NextFunction } from 'express';
|
|
7
|
+
import { ILogger } from '../utils';
|
|
8
|
+
import { UserJwtVerifier } from '../jwt/user-jwt-verifier';
|
|
13
9
|
export declare class RequireAuthMiddleware {
|
|
14
10
|
private readonly verifier;
|
|
15
11
|
private readonly logger;
|
|
16
|
-
/**
|
|
17
|
-
* @param {UserJwtVerifier} verifier - The token verification engine.
|
|
18
|
-
* @param {ILogger} [logger] - Optional logger instance.
|
|
19
|
-
*/
|
|
20
12
|
constructor(verifier: UserJwtVerifier, logger?: ILogger);
|
|
21
|
-
/**
|
|
22
|
-
* Core Express middleware handler for authenticating requests.
|
|
23
|
-
*
|
|
24
|
-
* @param {Request} req - Express request object.
|
|
25
|
-
* @param {Response} res - Express response object.
|
|
26
|
-
* @param {NextFunction} next - Express next function.
|
|
27
|
-
* @returns {Promise<void>}
|
|
28
|
-
*/
|
|
29
13
|
handle: (req: Request, res: Response, next: NextFunction) => Promise<void>;
|
|
30
14
|
private extractBearer;
|
|
31
15
|
private handleJwtError;
|
|
@@ -8,41 +8,28 @@ Object.defineProperty(exports, "__esModule", { value: true });
|
|
|
8
8
|
exports.RequireAuthMiddleware = void 0;
|
|
9
9
|
const jose_1 = require("jose");
|
|
10
10
|
const utils_1 = require("../utils");
|
|
11
|
-
|
|
12
|
-
* Middleware handling authentication validation.
|
|
13
|
-
* Extracts the JWT, verifies it via UserJwtVerifier, and attaches the AuthContext.
|
|
14
|
-
*/
|
|
11
|
+
const request_context_storage_1 = require("../context/request-context.storage");
|
|
15
12
|
class RequireAuthMiddleware {
|
|
16
|
-
/**
|
|
17
|
-
* @param {UserJwtVerifier} verifier - The token verification engine.
|
|
18
|
-
* @param {ILogger} [logger] - Optional logger instance.
|
|
19
|
-
*/
|
|
20
13
|
constructor(verifier, logger = utils_1.noopLogger) {
|
|
21
14
|
this.verifier = verifier;
|
|
22
15
|
this.logger = logger;
|
|
23
|
-
/**
|
|
24
|
-
* Core Express middleware handler for authenticating requests.
|
|
25
|
-
*
|
|
26
|
-
* @param {Request} req - Express request object.
|
|
27
|
-
* @param {Response} res - Express response object.
|
|
28
|
-
* @param {NextFunction} next - Express next function.
|
|
29
|
-
* @returns {Promise<void>}
|
|
30
|
-
*/
|
|
31
16
|
this.handle = async (req, res, next) => {
|
|
32
17
|
const token = this.extractBearer(req);
|
|
33
18
|
if (!token) {
|
|
34
|
-
(0, utils_1.failure)(res, req,
|
|
19
|
+
(0, utils_1.failure)(res, req, 'Missing Authorization header', 'UNAUTHORIZED', 401);
|
|
35
20
|
return;
|
|
36
21
|
}
|
|
37
22
|
try {
|
|
38
23
|
const { payload, context } = await this.verifier.buildAccessContext(token);
|
|
39
24
|
req.authPayload = payload;
|
|
40
25
|
req.authContext = context;
|
|
26
|
+
// Attach audit context. Will throw if requestContextStorage is not initialized.
|
|
27
|
+
request_context_storage_1.RequestContextProvider.setActor(context);
|
|
41
28
|
this.logger.debug({
|
|
42
29
|
requestId: req.id,
|
|
43
30
|
identity: context.accountId,
|
|
44
31
|
type: context.type,
|
|
45
|
-
},
|
|
32
|
+
}, 'Request authenticated');
|
|
46
33
|
next();
|
|
47
34
|
}
|
|
48
35
|
catch (err) {
|
|
@@ -55,29 +42,33 @@ class RequireAuthMiddleware {
|
|
|
55
42
|
--------------------------------------------------------------------------- */
|
|
56
43
|
extractBearer(req) {
|
|
57
44
|
const header = req.headers.authorization;
|
|
58
|
-
if (!header)
|
|
45
|
+
if (!header)
|
|
59
46
|
return null;
|
|
60
|
-
}
|
|
61
47
|
const [scheme, token] = header.trim().split(/\s+/);
|
|
62
|
-
if (scheme !==
|
|
48
|
+
if (scheme !== 'Bearer' || !token)
|
|
63
49
|
return null;
|
|
64
|
-
}
|
|
65
50
|
return token;
|
|
66
51
|
}
|
|
67
52
|
handleJwtError(err, req, res) {
|
|
68
53
|
if (err instanceof jose_1.errors.JWTExpired) {
|
|
69
|
-
(0, utils_1.failure)(res, req,
|
|
54
|
+
(0, utils_1.failure)(res, req, 'Your session has expired. Please log in again.', 'TOKEN_EXPIRED', 401);
|
|
70
55
|
return;
|
|
71
56
|
}
|
|
72
57
|
if (err instanceof jose_1.errors.JWTClaimValidationFailed ||
|
|
73
58
|
err instanceof jose_1.errors.JWSSignatureVerificationFailed ||
|
|
74
59
|
err instanceof jose_1.errors.JWSInvalid ||
|
|
75
60
|
err instanceof jose_1.errors.JWTInvalid) {
|
|
76
|
-
(0, utils_1.failure)(res, req,
|
|
61
|
+
(0, utils_1.failure)(res, req, 'Token is invalid or has been tampered with.', 'INVALID_TOKEN', 401);
|
|
62
|
+
return;
|
|
63
|
+
}
|
|
64
|
+
// Catch the specific Developer Error from RequestContextProvider
|
|
65
|
+
if (err instanceof Error && err.message.includes('AuditRequestContext')) {
|
|
66
|
+
this.logger.error({ err, requestId: req.id }, 'CRITICAL: RequireAuthMiddleware failed because requestContextStorage is not initialized. Check your middleware order.');
|
|
67
|
+
(0, utils_1.failure)(res, req, 'Internal server configuration error.', 'INTERNAL_SERVER_ERROR', 500);
|
|
77
68
|
return;
|
|
78
69
|
}
|
|
79
|
-
this.logger.error({ err, requestId: req.id },
|
|
80
|
-
(0, utils_1.failure)(res, req,
|
|
70
|
+
this.logger.error({ err, requestId: req.id }, 'RequireAuthMiddleware: unexpected error during JWT verification');
|
|
71
|
+
(0, utils_1.failure)(res, req, 'Authentication service is temporarily unavailable.', 'SERVICE_UNAVAILABLE', 503);
|
|
81
72
|
}
|
|
82
73
|
}
|
|
83
74
|
exports.RequireAuthMiddleware = RequireAuthMiddleware;
|
|
@@ -3,36 +3,11 @@
|
|
|
3
3
|
* @description Express middleware enforcing auth-service issued machine JWT verification
|
|
4
4
|
* for service-to-service communication endpoints.
|
|
5
5
|
*/
|
|
6
|
-
import { RequestHandler } from
|
|
7
|
-
import { ILogger } from
|
|
8
|
-
/**
|
|
9
|
-
* Configuration options for machine authentication middleware.
|
|
10
|
-
*/
|
|
6
|
+
import { RequestHandler } from 'express';
|
|
7
|
+
import { ILogger } from '../utils/logger.util';
|
|
11
8
|
export interface RequireMachineOptions {
|
|
12
|
-
/**
|
|
13
|
-
* Auth-service JWKS endpoint used to verify machine JWT signatures.
|
|
14
|
-
*/
|
|
15
9
|
authServiceJwksUri?: string;
|
|
16
|
-
/**
|
|
17
|
-
* Service identifiers allowed to access this endpoint.
|
|
18
|
-
*/
|
|
19
10
|
allowedServices?: string[];
|
|
20
|
-
/**
|
|
21
|
-
* Optional structured logger.
|
|
22
|
-
*/
|
|
23
11
|
logger?: ILogger;
|
|
24
12
|
}
|
|
25
|
-
/**
|
|
26
|
-
* Creates middleware requiring a valid machine JWT.
|
|
27
|
-
*
|
|
28
|
-
* Flow:
|
|
29
|
-
* - Extract Bearer token
|
|
30
|
-
* - Verify RS256 signature using auth-service JWKS
|
|
31
|
-
* - Validate machine token claims
|
|
32
|
-
* - Apply service allowlist
|
|
33
|
-
* - Attach MachineContext
|
|
34
|
-
*
|
|
35
|
-
* @param {RequireMachineOptions} [options] - Middleware configuration options.
|
|
36
|
-
* @returns {RequestHandler} Express middleware handler.
|
|
37
|
-
*/
|
|
38
13
|
export declare function requireMachine(options?: RequireMachineOptions): RequestHandler;
|
|
@@ -42,29 +42,20 @@ exports.requireMachine = requireMachine;
|
|
|
42
42
|
const jose = __importStar(require("jose"));
|
|
43
43
|
const logger_util_1 = require("../utils/logger.util");
|
|
44
44
|
const machine_jwt_verifier_1 = require("../jwt/machine-jwt-verifier");
|
|
45
|
-
|
|
46
|
-
* Creates middleware requiring a valid machine JWT.
|
|
47
|
-
*
|
|
48
|
-
* Flow:
|
|
49
|
-
* - Extract Bearer token
|
|
50
|
-
* - Verify RS256 signature using auth-service JWKS
|
|
51
|
-
* - Validate machine token claims
|
|
52
|
-
* - Apply service allowlist
|
|
53
|
-
* - Attach MachineContext
|
|
54
|
-
*
|
|
55
|
-
* @param {RequireMachineOptions} [options] - Middleware configuration options.
|
|
56
|
-
* @returns {RequestHandler} Express middleware handler.
|
|
57
|
-
*/
|
|
45
|
+
const request_context_storage_1 = require("../context/request-context.storage");
|
|
58
46
|
function requireMachine(options = {}) {
|
|
59
47
|
const logger = options.logger ?? logger_util_1.noopLogger;
|
|
60
48
|
const verifier = new machine_jwt_verifier_1.MachineJwtVerifier(options.authServiceJwksUri);
|
|
61
49
|
const { allowedServices } = options;
|
|
62
50
|
return async (req, res, next) => {
|
|
63
51
|
const header = req.headers.authorization;
|
|
64
|
-
if (!header?.startsWith(
|
|
52
|
+
if (!header?.startsWith('Bearer ')) {
|
|
65
53
|
res.status(401).json({
|
|
66
54
|
success: false,
|
|
67
|
-
error: {
|
|
55
|
+
error: {
|
|
56
|
+
code: 'UNAUTHORIZED',
|
|
57
|
+
message: 'Missing Authorization header',
|
|
58
|
+
},
|
|
68
59
|
});
|
|
69
60
|
return;
|
|
70
61
|
}
|
|
@@ -72,31 +63,33 @@ function requireMachine(options = {}) {
|
|
|
72
63
|
try {
|
|
73
64
|
const payload = await verifier.verify(token);
|
|
74
65
|
if (allowedServices && !allowedServices.includes(payload.serviceId)) {
|
|
75
|
-
logger.warn({ requestId: req.id, serviceId: payload.serviceId },
|
|
66
|
+
logger.warn({ requestId: req.id, serviceId: payload.serviceId }, '[requireMachine] Service rejected');
|
|
76
67
|
res.status(403).json({
|
|
77
68
|
success: false,
|
|
78
69
|
error: {
|
|
79
|
-
code:
|
|
80
|
-
message:
|
|
70
|
+
code: 'FORBIDDEN',
|
|
71
|
+
message: 'Service is not permitted to access this endpoint',
|
|
81
72
|
},
|
|
82
73
|
});
|
|
83
74
|
return;
|
|
84
75
|
}
|
|
85
76
|
const context = {
|
|
86
|
-
type:
|
|
77
|
+
type: 'machine',
|
|
87
78
|
serviceId: payload.serviceId,
|
|
88
79
|
scopes: payload.scopes,
|
|
89
80
|
};
|
|
90
81
|
req.authPayload = payload;
|
|
91
82
|
req.authContext = context;
|
|
92
|
-
|
|
83
|
+
// Attach audit context. Will throw if requestContextStorage is not initialized.
|
|
84
|
+
request_context_storage_1.RequestContextProvider.setActor(context);
|
|
85
|
+
logger.debug({ requestId: req.id, serviceId: payload.serviceId, jti: payload.jti }, '[requireMachine] Machine authenticated');
|
|
93
86
|
next();
|
|
94
87
|
}
|
|
95
88
|
catch (err) {
|
|
96
89
|
if (err instanceof jose.errors.JWTExpired) {
|
|
97
90
|
res.status(401).json({
|
|
98
91
|
success: false,
|
|
99
|
-
error: { code:
|
|
92
|
+
error: { code: 'TOKEN_EXPIRED', message: 'Machine token expired' },
|
|
100
93
|
});
|
|
101
94
|
return;
|
|
102
95
|
}
|
|
@@ -106,16 +99,28 @@ function requireMachine(options = {}) {
|
|
|
106
99
|
err instanceof jose.errors.JWTInvalid) {
|
|
107
100
|
res.status(401).json({
|
|
108
101
|
success: false,
|
|
109
|
-
error: { code:
|
|
102
|
+
error: { code: 'INVALID_TOKEN', message: 'Invalid machine token' },
|
|
103
|
+
});
|
|
104
|
+
return;
|
|
105
|
+
}
|
|
106
|
+
// Catch the specific Developer Error from RequestContextProvider
|
|
107
|
+
if (err instanceof Error && err.message.includes('AuditRequestContext')) {
|
|
108
|
+
logger.error({ err, requestId: req.id }, 'CRITICAL: [requireMachine] failed because requestContextStorage is not initialized. Check middleware order.');
|
|
109
|
+
res.status(500).json({
|
|
110
|
+
success: false,
|
|
111
|
+
error: {
|
|
112
|
+
code: 'INTERNAL_SERVER_ERROR',
|
|
113
|
+
message: 'Internal server configuration error',
|
|
114
|
+
},
|
|
110
115
|
});
|
|
111
116
|
return;
|
|
112
117
|
}
|
|
113
|
-
logger.error({ err, requestId: req.id },
|
|
118
|
+
logger.error({ err, requestId: req.id }, '[requireMachine] Machine authentication failure');
|
|
114
119
|
res.status(503).json({
|
|
115
120
|
success: false,
|
|
116
121
|
error: {
|
|
117
|
-
code:
|
|
118
|
-
message:
|
|
122
|
+
code: 'SERVICE_UNAVAILABLE',
|
|
123
|
+
message: 'Machine authentication temporarily unavailable',
|
|
119
124
|
},
|
|
120
125
|
});
|
|
121
126
|
}
|
|
@@ -9,8 +9,7 @@ exports.createRequireOrgPermissionFromBody = void 0;
|
|
|
9
9
|
const createRequireOrgPermissionFromBody = (requireOrgPermission) => {
|
|
10
10
|
const requireOrgPermissionFromBody = (permission) => {
|
|
11
11
|
return async (req, res, next) => {
|
|
12
|
-
const organizationId = req.body?.organizationId ??
|
|
13
|
-
req.validated?.body?.organizationId;
|
|
12
|
+
const organizationId = req.body?.organizationId ?? req.validated?.body?.organizationId;
|
|
14
13
|
if (!organizationId) {
|
|
15
14
|
return next();
|
|
16
15
|
}
|
|
@@ -2,9 +2,9 @@
|
|
|
2
2
|
* @file require-org-permission.middleware.ts
|
|
3
3
|
* @description Express middleware generator to enforce organization-scoped permission checks.
|
|
4
4
|
*/
|
|
5
|
-
import { RequestHandler } from
|
|
6
|
-
import { Permission } from
|
|
7
|
-
import { PermissionCacheService } from
|
|
5
|
+
import { RequestHandler } from 'express';
|
|
6
|
+
import { Permission } from '../enums';
|
|
7
|
+
import { PermissionCacheService } from '../authorization/permission-cache.service';
|
|
8
8
|
/**
|
|
9
9
|
* Creates requireOrgPermission middleware factory.
|
|
10
10
|
*
|