@onlineapps/service-wrapper 2.1.58 → 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
package/src/ConfigLoader.js
CHANGED
|
@@ -303,6 +303,20 @@ class ConfigLoader {
|
|
|
303
303
|
const validationProof = this.loadValidationProof(basePath);
|
|
304
304
|
const nodeEnv = runtimeCfg.get('nodeEnv');
|
|
305
305
|
|
|
306
|
+
// Load service-wrapper runtime defaults (wrapper config only)
|
|
307
|
+
const runtimeDefaultsPath = path.join(__dirname, '..', 'config', 'runtime-defaults.json');
|
|
308
|
+
let runtimeDefaults = {};
|
|
309
|
+
try {
|
|
310
|
+
const runtimeDefaultsRaw = fs.readFileSync(runtimeDefaultsPath, 'utf8');
|
|
311
|
+
const parsed = JSON.parse(runtimeDefaultsRaw);
|
|
312
|
+
runtimeDefaults = parsed && typeof parsed === 'object' && parsed.defaults ? parsed.defaults : {};
|
|
313
|
+
} catch (error) {
|
|
314
|
+
throw new Error(`[ConfigLoader] Failed to load runtime defaults - Expected readable JSON at ${runtimeDefaultsPath}. ${error.message}`);
|
|
315
|
+
}
|
|
316
|
+
|
|
317
|
+
// Deep-merge defaults into wrapper config (service overrides win)
|
|
318
|
+
const mergedWrapper = this.deepMerge(runtimeDefaults.wrapper || {}, serviceConfig.wrapper || {});
|
|
319
|
+
|
|
306
320
|
// Include any custom top-level sections from config.json (e.g., storage, smtp, etc.)
|
|
307
321
|
// Keep service-wrapper owned sections (service, wrapper) explicit and stable.
|
|
308
322
|
const customSections = {};
|
|
@@ -321,7 +335,7 @@ class ConfigLoader {
|
|
|
321
335
|
url: serviceConfig.service.url,
|
|
322
336
|
env: nodeEnv
|
|
323
337
|
},
|
|
324
|
-
wrapper:
|
|
338
|
+
wrapper: mergedWrapper,
|
|
325
339
|
operations,
|
|
326
340
|
validationProof,
|
|
327
341
|
...customSections
|
|
@@ -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, () => {
|