@onlineapps/service-wrapper 2.1.59 → 2.1.60
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/package.json
CHANGED
|
@@ -0,0 +1,88 @@
|
|
|
1
|
+
'use strict';
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* Create an Express middleware that enforces required account context for business APIs.
|
|
5
|
+
*
|
|
6
|
+
* Rules:
|
|
7
|
+
* - Applies only for paths that match `requirePathPrefixes` and do NOT match `excludePathPrefixes`.
|
|
8
|
+
* - Requires header `headerName` to be a positive integer.
|
|
9
|
+
* - Sets `req.account_id` as integer.
|
|
10
|
+
*
|
|
11
|
+
* @param {Object} options
|
|
12
|
+
* @param {string} options.serviceName
|
|
13
|
+
* @param {Object} options.accountContext
|
|
14
|
+
* @param {boolean} options.accountContext.enabled
|
|
15
|
+
* @param {string} options.accountContext.headerName
|
|
16
|
+
* @param {string[]} options.accountContext.requirePathPrefixes
|
|
17
|
+
* @param {string[]} options.accountContext.excludePathPrefixes
|
|
18
|
+
* @returns {Function} Express middleware
|
|
19
|
+
*/
|
|
20
|
+
function createAccountContextMiddleware(options = {}) {
|
|
21
|
+
const serviceName = options.serviceName;
|
|
22
|
+
const accountContext = options.accountContext || {};
|
|
23
|
+
|
|
24
|
+
if (!serviceName || typeof serviceName !== 'string') {
|
|
25
|
+
throw new Error('[service-wrapper][AccountContext] Missing dependency - Expected serviceName (string)');
|
|
26
|
+
}
|
|
27
|
+
if (typeof accountContext !== 'object' || Array.isArray(accountContext)) {
|
|
28
|
+
throw new Error('[service-wrapper][AccountContext] Invalid configuration - Expected accountContext to be an object');
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
const {
|
|
32
|
+
enabled,
|
|
33
|
+
headerName,
|
|
34
|
+
requirePathPrefixes,
|
|
35
|
+
excludePathPrefixes
|
|
36
|
+
} = accountContext;
|
|
37
|
+
|
|
38
|
+
if (typeof enabled !== 'boolean') {
|
|
39
|
+
throw new Error(`[service-wrapper][AccountContext] Invalid configuration - wrapper.accountContext.enabled must be boolean, got: ${typeof enabled}`);
|
|
40
|
+
}
|
|
41
|
+
if (typeof headerName !== 'string' || headerName.trim() === '') {
|
|
42
|
+
throw new Error(`[service-wrapper][AccountContext] Invalid configuration - wrapper.accountContext.headerName must be non-empty string, got: ${headerName}`);
|
|
43
|
+
}
|
|
44
|
+
if (!Array.isArray(requirePathPrefixes) || requirePathPrefixes.some(p => typeof p !== 'string')) {
|
|
45
|
+
throw new Error('[service-wrapper][AccountContext] Invalid configuration - wrapper.accountContext.requirePathPrefixes must be string[]');
|
|
46
|
+
}
|
|
47
|
+
if (!Array.isArray(excludePathPrefixes) || excludePathPrefixes.some(p => typeof p !== 'string')) {
|
|
48
|
+
throw new Error('[service-wrapper][AccountContext] Invalid configuration - wrapper.accountContext.excludePathPrefixes must be string[]');
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
const requirePrefixes = requirePathPrefixes;
|
|
52
|
+
const excludePrefixes = excludePathPrefixes;
|
|
53
|
+
|
|
54
|
+
return function accountContextMiddleware(req, res, next) {
|
|
55
|
+
if (!enabled) {
|
|
56
|
+
return next();
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
const path = req && typeof req.path === 'string' ? req.path : '';
|
|
60
|
+
if (!requirePrefixes.some(prefix => path.startsWith(prefix))) {
|
|
61
|
+
return next();
|
|
62
|
+
}
|
|
63
|
+
if (excludePrefixes.some(prefix => path.startsWith(prefix))) {
|
|
64
|
+
return next();
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
const rawAccountId = req.headers ? req.headers[headerName] : undefined;
|
|
68
|
+
if (!rawAccountId) {
|
|
69
|
+
return res.status(400).json({
|
|
70
|
+
error: `[${serviceName}][Multitenancy] Missing account context - Expected request header '${headerName}' (INT)`
|
|
71
|
+
});
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
const accountId = Number.parseInt(String(rawAccountId), 10);
|
|
75
|
+
if (!Number.isInteger(accountId) || accountId <= 0) {
|
|
76
|
+
return res.status(400).json({
|
|
77
|
+
error: `[${serviceName}][Multitenancy] Invalid account context - Expected '${headerName}' to be a positive integer`
|
|
78
|
+
});
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
req.account_id = accountId;
|
|
82
|
+
return next();
|
|
83
|
+
};
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
module.exports = { createAccountContextMiddleware };
|
|
87
|
+
|
|
88
|
+
|
package/src/index.js
CHANGED
|
@@ -16,6 +16,7 @@ const ServiceWrapper = require('./ServiceWrapper');
|
|
|
16
16
|
const { ConfigLoader } = require('./ConfigLoader');
|
|
17
17
|
const runtimeCfg = require('./config');
|
|
18
18
|
const pkg = require('../package.json');
|
|
19
|
+
const { createAccountContextMiddleware } = require('./createAccountContextMiddleware');
|
|
19
20
|
|
|
20
21
|
// Note: WorkflowProcessor and ApiCaller functionality has been moved to connectors:
|
|
21
22
|
// - WorkflowProcessor -> @onlineapps/conn-orch-orchestrator
|
|
@@ -58,6 +59,14 @@ async function bootstrap(serviceRoot, options = {}) {
|
|
|
58
59
|
|
|
59
60
|
console.log(`Starting ${config.service.name} v${config.service.version}...`);
|
|
60
61
|
|
|
62
|
+
// 0. Apply shared request context middleware (account context for multitenancy)
|
|
63
|
+
// Enforced centrally for all business services: /api/** must have account-id (except /api/v1/specification).
|
|
64
|
+
const accountContextMw = createAccountContextMiddleware({
|
|
65
|
+
serviceName: config.service.name,
|
|
66
|
+
accountContext: config.wrapper?.accountContext
|
|
67
|
+
});
|
|
68
|
+
app.use(accountContextMw);
|
|
69
|
+
|
|
61
70
|
// 1. Start HTTP server
|
|
62
71
|
const PORT = config.service.port;
|
|
63
72
|
const server = app.listen(PORT, () => {
|