soajs.controller 4.3.10 → 4.3.11
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/mw/awareness/ha.js +3 -1
- package/mw/gotoService/index.js +3 -1
- package/mw/gotoService/lib/preRedirect.js +10 -8
- package/mw/gotoService/redirectToService.js +1 -1
- package/mw/url/index.js +3 -1
- package/package.json +1 -1
- package/test/unit/utilities/utils.js +169 -19
- package/utilities/utils.js +73 -1
package/mw/awareness/ha.js
CHANGED
|
@@ -176,7 +176,9 @@ let lib = {
|
|
|
176
176
|
}, function () {
|
|
177
177
|
awarenessCache = myCache;
|
|
178
178
|
param.log.debug("Awareness cache rebuilt successfully");
|
|
179
|
-
param.log.debug(
|
|
179
|
+
if (param.log.debug()) {
|
|
180
|
+
param.log.debug(JSON.stringify(awarenessCache));
|
|
181
|
+
}
|
|
180
182
|
|
|
181
183
|
let cacheTTL = registryModule.get().serviceConfig.awareness.cacheTTL;
|
|
182
184
|
if (cacheTTL) {
|
package/mw/gotoService/index.js
CHANGED
|
@@ -82,7 +82,9 @@ module.exports = (configuration) => {
|
|
|
82
82
|
req.soajs.registry.services[service_n]) {
|
|
83
83
|
req.soajs.log.debug(req.soajs.registry.services[service_n]);
|
|
84
84
|
}
|
|
85
|
-
req.soajs.log.warn(
|
|
85
|
+
if (req.soajs.log.warn()) {
|
|
86
|
+
req.soajs.log.warn(JSON.stringify({ "url": req.url, "headers": req.headers }));
|
|
87
|
+
}
|
|
86
88
|
return req.soajs.controllerResponse(core.error.getError(130));
|
|
87
89
|
}
|
|
88
90
|
|
|
@@ -28,14 +28,16 @@ module.exports = (req, res, core, cb) => {
|
|
|
28
28
|
return req.soajs.controllerResponse(core.error.getError(131));
|
|
29
29
|
}
|
|
30
30
|
let nextStep = function (host, port, fullURI) {
|
|
31
|
-
req.soajs.log.info(
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
31
|
+
if (req.soajs.log.info()) {
|
|
32
|
+
req.soajs.log.info(JSON.stringify({
|
|
33
|
+
"serviceName": restServiceParams.name,
|
|
34
|
+
"host": host,
|
|
35
|
+
"version": restServiceParams.version,
|
|
36
|
+
"url": restServiceParams.url,
|
|
37
|
+
"port": restServiceParams.registry.port,
|
|
38
|
+
"header": req.headers
|
|
39
|
+
}));
|
|
40
|
+
}
|
|
39
41
|
|
|
40
42
|
let requestTOR = restServiceParams.registry.requestTimeoutRenewal || config.requestTimeoutRenewal;
|
|
41
43
|
let requestTO = restServiceParams.registry.requestTimeout || config.requestTimeout;
|
|
@@ -225,7 +225,7 @@ module.exports = (configuration) => {
|
|
|
225
225
|
if (monitor && !monitor_service_blacklist && monitor.req_info) {
|
|
226
226
|
monitoObj.port = req.soajs.controller.serviceParams.registry.port;
|
|
227
227
|
monitoObj.url = req.soajs.controller.serviceParams.url;
|
|
228
|
-
monitoObj.host =
|
|
228
|
+
monitoObj.host = obj.host;
|
|
229
229
|
}
|
|
230
230
|
if (monitor && !monitor_service_blacklist && monitor.req_header) {
|
|
231
231
|
monitoObj.headers = req.headers;
|
package/mw/url/index.js
CHANGED
|
@@ -64,7 +64,9 @@ module.exports = (configuration) => {
|
|
|
64
64
|
!req.soajs.registry.services[req.soajs.controller.serviceParams.service_n]
|
|
65
65
|
)) {
|
|
66
66
|
req.soajs.log.fatal("Service [" + req.soajs.controller.serviceParams.service_n + "] URL [" + req.url + "] couldn't be matched to a service or the service entry in registry is missing [port || hosts]");
|
|
67
|
-
req.soajs.log.warn(
|
|
67
|
+
if (req.soajs.log.warn()) {
|
|
68
|
+
req.soajs.log.warn(JSON.stringify({ "url": req.url, "headers": req.headers }));
|
|
69
|
+
}
|
|
68
70
|
return req.soajs.controllerResponse(core.error.getError(130));
|
|
69
71
|
}
|
|
70
72
|
|
package/package.json
CHANGED
|
@@ -1,10 +1,82 @@
|
|
|
1
1
|
"use strict"
|
|
2
|
+
const assert = require('assert');
|
|
2
3
|
const helper = require("../../helper.js");
|
|
3
4
|
|
|
4
5
|
let utils = helper.requireModule('./utilities/utils.js');
|
|
5
6
|
|
|
6
7
|
describe("Testing utilities", () => {
|
|
7
8
|
|
|
9
|
+
const TOKEN = "e2d0ffb0f0e34bd6a8e0a0b4a1d1b6c1f2a3b4c5";
|
|
10
|
+
const EKEY = "aa39b5490c4a4ed0e56d7ec1232a428f771e8bb83cfcee16de14f735d0f5da58";
|
|
11
|
+
const IKEY = "38145c67717c73d3febd16df38abf311";
|
|
12
|
+
|
|
13
|
+
let logged = {
|
|
14
|
+
"error": [],
|
|
15
|
+
"warn": []
|
|
16
|
+
};
|
|
17
|
+
|
|
18
|
+
/**
|
|
19
|
+
* bunyan semantics: log.<level>() with no argument returns whether that level is
|
|
20
|
+
* enabled, log.<level>(input) writes the record. The gateway relies on the first
|
|
21
|
+
* form to skip building log payloads that would be discarded.
|
|
22
|
+
*/
|
|
23
|
+
let makeLog = (enabled) => {
|
|
24
|
+
return {
|
|
25
|
+
error: (input) => {
|
|
26
|
+
if (input === undefined) {
|
|
27
|
+
return enabled;
|
|
28
|
+
}
|
|
29
|
+
logged.error.push(input);
|
|
30
|
+
},
|
|
31
|
+
warn: (input) => {
|
|
32
|
+
if (input === undefined) {
|
|
33
|
+
return enabled;
|
|
34
|
+
}
|
|
35
|
+
logged.warn.push(input);
|
|
36
|
+
}
|
|
37
|
+
};
|
|
38
|
+
};
|
|
39
|
+
|
|
40
|
+
let makeReq = (enabled) => {
|
|
41
|
+
return {
|
|
42
|
+
method: "GET",
|
|
43
|
+
url: "/urac/user/getprofile?access_token=" + TOKEN + "&key=" + EKEY + "&id=42",
|
|
44
|
+
headers: {
|
|
45
|
+
"access_token": TOKEN,
|
|
46
|
+
"key": EKEY,
|
|
47
|
+
"authorization": "Bearer " + TOKEN
|
|
48
|
+
},
|
|
49
|
+
getClientIP: () => {
|
|
50
|
+
return "196.218.44.17";
|
|
51
|
+
},
|
|
52
|
+
getClientUserAgent: () => {
|
|
53
|
+
return "mocha-agent";
|
|
54
|
+
},
|
|
55
|
+
soajs: {
|
|
56
|
+
log: makeLog(enabled),
|
|
57
|
+
controller: {
|
|
58
|
+
serviceParams: {
|
|
59
|
+
name: "urac",
|
|
60
|
+
version: "3",
|
|
61
|
+
parsedUrl: { pathname: "/urac/user/getprofile" }
|
|
62
|
+
}
|
|
63
|
+
},
|
|
64
|
+
tenant: {
|
|
65
|
+
id: "5c0e74ba9acc3c5a84a51259",
|
|
66
|
+
code: "TSTM",
|
|
67
|
+
key: { iKey: IKEY, eKey: EKEY },
|
|
68
|
+
application: { product: "TPROD", package: "TPROD_BASIC" }
|
|
69
|
+
},
|
|
70
|
+
buildResponse: (input) => {
|
|
71
|
+
return input;
|
|
72
|
+
},
|
|
73
|
+
controllerResponse: (input) => {
|
|
74
|
+
return input;
|
|
75
|
+
}
|
|
76
|
+
}
|
|
77
|
+
};
|
|
78
|
+
};
|
|
79
|
+
|
|
8
80
|
let res = {
|
|
9
81
|
status: () => {
|
|
10
82
|
return {
|
|
@@ -18,24 +90,12 @@ describe("Testing utilities", () => {
|
|
|
18
90
|
}
|
|
19
91
|
};
|
|
20
92
|
|
|
21
|
-
let req =
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
warn: (input) => {
|
|
28
|
-
// console.error(input);
|
|
29
|
-
}
|
|
30
|
-
},
|
|
31
|
-
buildResponse: (input) => {
|
|
32
|
-
return input;
|
|
33
|
-
},
|
|
34
|
-
controllerResponse: (input) => {
|
|
35
|
-
return input;
|
|
36
|
-
}
|
|
37
|
-
}
|
|
38
|
-
};
|
|
93
|
+
let req = makeReq(true);
|
|
94
|
+
|
|
95
|
+
beforeEach(() => {
|
|
96
|
+
logged.error = [];
|
|
97
|
+
logged.warn = [];
|
|
98
|
+
});
|
|
39
99
|
|
|
40
100
|
it("logErrors - number error", (done) => {
|
|
41
101
|
utils.logErrors(123, req, res, (error) => {
|
|
@@ -76,4 +136,94 @@ describe("Testing utilities", () => {
|
|
|
76
136
|
utils.controllerErrorHandler({ "code": 200, "msg": "dummy200" }, req, res, null);
|
|
77
137
|
done();
|
|
78
138
|
});
|
|
79
|
-
|
|
139
|
+
|
|
140
|
+
it("controllerErrorHandler - emits the redacted context at error level", (done) => {
|
|
141
|
+
let r = makeReq(true);
|
|
142
|
+
utils.controllerErrorHandler({ "code": 401, "msg": "The access token provided is invalid." }, r, res, null);
|
|
143
|
+
|
|
144
|
+
assert.strictEqual(logged.error.length, 1);
|
|
145
|
+
let ctx = JSON.parse(logged.error[0]);
|
|
146
|
+
assert.strictEqual(ctx.code, 401);
|
|
147
|
+
assert.strictEqual(ctx.msg, "The access token provided is invalid.");
|
|
148
|
+
assert.strictEqual(ctx.method, "GET");
|
|
149
|
+
assert.strictEqual(ctx.service, "urac");
|
|
150
|
+
assert.strictEqual(ctx.version, "3");
|
|
151
|
+
assert.strictEqual(ctx.api, "/urac/user/getprofile");
|
|
152
|
+
assert.strictEqual(ctx.ip, "196.218.44.17");
|
|
153
|
+
assert.strictEqual(ctx.ua, "mocha-agent");
|
|
154
|
+
assert.deepStrictEqual(ctx.tenant, { "id": "5c0e74ba9acc3c5a84a51259", "code": "TSTM" });
|
|
155
|
+
assert.strictEqual(ctx.iKey, IKEY);
|
|
156
|
+
assert.deepStrictEqual(ctx.application, { "product": "TPROD", "package": "TPROD_BASIC" });
|
|
157
|
+
done();
|
|
158
|
+
});
|
|
159
|
+
|
|
160
|
+
it("controllerErrorHandler - masks credentials and never logs headers", (done) => {
|
|
161
|
+
let r = makeReq(true);
|
|
162
|
+
utils.controllerErrorHandler({ "code": 401, "msg": "bad token" }, r, res, null);
|
|
163
|
+
|
|
164
|
+
let line = logged.error[0];
|
|
165
|
+
assert.strictEqual(line.indexOf(TOKEN), -1, "access_token leaked into the error context");
|
|
166
|
+
assert.strictEqual(line.indexOf(EKEY), -1, "eKey leaked into the error context");
|
|
167
|
+
assert.strictEqual(line.indexOf("authorization"), -1, "headers leaked into the error context");
|
|
168
|
+
|
|
169
|
+
let ctx = JSON.parse(line);
|
|
170
|
+
assert.strictEqual(ctx.url, "/urac/user/getprofile?access_token=***&key=***&id=42");
|
|
171
|
+
done();
|
|
172
|
+
});
|
|
173
|
+
|
|
174
|
+
it("controllerErrorHandler - numeric error resolves code and message", (done) => {
|
|
175
|
+
let r = makeReq(true);
|
|
176
|
+
utils.controllerErrorHandler(143, r, res, null);
|
|
177
|
+
|
|
178
|
+
let ctx = JSON.parse(logged.error[0]);
|
|
179
|
+
assert.strictEqual(ctx.code, 143);
|
|
180
|
+
assert.strictEqual(ctx.msg, "invalid_request: Malformed auth header");
|
|
181
|
+
done();
|
|
182
|
+
});
|
|
183
|
+
|
|
184
|
+
it("controllerErrorHandler - includes the resolved username when present", (done) => {
|
|
185
|
+
let r = makeReq(true);
|
|
186
|
+
r.soajs.uracDriver = { "username": "owner" };
|
|
187
|
+
utils.controllerErrorHandler({ "code": 401, "msg": "bad token" }, r, res, null);
|
|
188
|
+
|
|
189
|
+
let ctx = JSON.parse(logged.error[0]);
|
|
190
|
+
assert.strictEqual(ctx.username, "owner");
|
|
191
|
+
done();
|
|
192
|
+
});
|
|
193
|
+
|
|
194
|
+
it("controllerErrorHandler - degraded request without tenant or serviceParams", (done) => {
|
|
195
|
+
let r = makeReq(true);
|
|
196
|
+
r.soajs.controller = {};
|
|
197
|
+
delete r.soajs.tenant;
|
|
198
|
+
utils.controllerErrorHandler(130, r, res, null);
|
|
199
|
+
|
|
200
|
+
let ctx = JSON.parse(logged.error[0]);
|
|
201
|
+
assert.strictEqual(ctx.service, null);
|
|
202
|
+
assert.strictEqual(ctx.version, null);
|
|
203
|
+
assert.strictEqual(ctx.api, null);
|
|
204
|
+
assert.strictEqual(ctx.tenant, undefined);
|
|
205
|
+
assert.strictEqual(ctx.iKey, undefined);
|
|
206
|
+
assert.strictEqual(ctx.application, undefined);
|
|
207
|
+
done();
|
|
208
|
+
});
|
|
209
|
+
|
|
210
|
+
it("controllerErrorHandler - skips both payloads when the levels are off", (done) => {
|
|
211
|
+
let r = makeReq(false);
|
|
212
|
+
utils.controllerErrorHandler({ "code": 401, "msg": "bad token" }, r, res, null);
|
|
213
|
+
|
|
214
|
+
assert.strictEqual(logged.error.length, 0);
|
|
215
|
+
assert.strictEqual(logged.warn.length, 0);
|
|
216
|
+
done();
|
|
217
|
+
});
|
|
218
|
+
|
|
219
|
+
it("controllerErrorHandler - the warn dump still carries the raw headers", (done) => {
|
|
220
|
+
let r = makeReq(true);
|
|
221
|
+
utils.controllerErrorHandler({ "code": 401, "msg": "bad token" }, r, res, null);
|
|
222
|
+
|
|
223
|
+
assert.strictEqual(logged.warn.length, 1);
|
|
224
|
+
let dump = JSON.parse(logged.warn[0]);
|
|
225
|
+
assert.strictEqual(dump.headers.access_token, TOKEN);
|
|
226
|
+
assert.strictEqual(dump.url, r.url);
|
|
227
|
+
done();
|
|
228
|
+
});
|
|
229
|
+
});
|
package/utilities/utils.js
CHANGED
|
@@ -11,6 +11,72 @@
|
|
|
11
11
|
const coreModules = require("soajs.core.modules");
|
|
12
12
|
let core = coreModules.core;
|
|
13
13
|
|
|
14
|
+
//NOTE: credentials that must never reach the logs, they are masked in the query string
|
|
15
|
+
const SENSITIVE_QS = /([?&](?:access_token|refresh_token|key)=)[^&]*/gi;
|
|
16
|
+
|
|
17
|
+
/**
|
|
18
|
+
* Mask credentials in a url query string
|
|
19
|
+
*
|
|
20
|
+
* @param url
|
|
21
|
+
* @returns {string|null}
|
|
22
|
+
*/
|
|
23
|
+
function redactUrl(url) {
|
|
24
|
+
return url ? url.replace(SENSITIVE_QS, '$1***') : null;
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
/**
|
|
28
|
+
* Build a compact and redacted context for an error response.
|
|
29
|
+
* NOTE: headers are never included, they carry access_token, key, authorization and soajsinjectobj.
|
|
30
|
+
*
|
|
31
|
+
* @param err
|
|
32
|
+
* @param req
|
|
33
|
+
* @returns {Object}
|
|
34
|
+
*/
|
|
35
|
+
function errorContext(err, req) {
|
|
36
|
+
let soajs = req.soajs || {};
|
|
37
|
+
let serviceParams = (soajs.controller && soajs.controller.serviceParams) || {};
|
|
38
|
+
let tenant = soajs.tenant || {};
|
|
39
|
+
|
|
40
|
+
let code = null;
|
|
41
|
+
let msg = null;
|
|
42
|
+
if (typeof err === "number") {
|
|
43
|
+
code = err;
|
|
44
|
+
msg = core.error.generate(err).message;
|
|
45
|
+
} else if (err && typeof err === "object") {
|
|
46
|
+
code = err.code || err.status || null;
|
|
47
|
+
msg = err.msg || err.message || null;
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
let context = {
|
|
51
|
+
"code": code,
|
|
52
|
+
"msg": msg,
|
|
53
|
+
"method": req.method,
|
|
54
|
+
"service": serviceParams.name || serviceParams.service_n || null,
|
|
55
|
+
"version": serviceParams.version || null,
|
|
56
|
+
"api": (serviceParams.parsedUrl && serviceParams.parsedUrl.pathname) || null,
|
|
57
|
+
"url": redactUrl(req.url),
|
|
58
|
+
"ip": req.getClientIP ? req.getClientIP() : null,
|
|
59
|
+
"ua": req.getClientUserAgent ? req.getClientUserAgent() : null
|
|
60
|
+
};
|
|
61
|
+
if (tenant.id) {
|
|
62
|
+
context.tenant = { "id": tenant.id, "code": tenant.code };
|
|
63
|
+
}
|
|
64
|
+
//NOTE: iKey only, the eKey is the credential the client authenticates with
|
|
65
|
+
if (tenant.key && tenant.key.iKey) {
|
|
66
|
+
context.iKey = tenant.key.iKey;
|
|
67
|
+
}
|
|
68
|
+
if (tenant.application) {
|
|
69
|
+
context.application = {
|
|
70
|
+
"product": tenant.application.product,
|
|
71
|
+
"package": tenant.application.package
|
|
72
|
+
};
|
|
73
|
+
}
|
|
74
|
+
if (soajs.uracDriver && soajs.uracDriver.username) {
|
|
75
|
+
context.username = soajs.uracDriver.username;
|
|
76
|
+
}
|
|
77
|
+
return context;
|
|
78
|
+
}
|
|
79
|
+
|
|
14
80
|
//-------------------------- ERROR Handling MW - service & controller
|
|
15
81
|
/**
|
|
16
82
|
*
|
|
@@ -77,7 +143,13 @@ function controllerClientErrorHandler(err, req, res, next) {
|
|
|
77
143
|
*/
|
|
78
144
|
function controllerErrorHandler(err, req, res, next) {
|
|
79
145
|
|
|
80
|
-
req.soajs.log.
|
|
146
|
+
if (req.soajs.log.error()) {
|
|
147
|
+
req.soajs.log.error(JSON.stringify(errorContext(err, req)));
|
|
148
|
+
}
|
|
149
|
+
//NOTE: the full header dump stays at warn, it is a debugging aid and it is not redacted
|
|
150
|
+
if (req.soajs.log.warn()) {
|
|
151
|
+
req.soajs.log.warn(JSON.stringify({ "url": req.url, "headers": req.headers }));
|
|
152
|
+
}
|
|
81
153
|
|
|
82
154
|
if (err.code && err.msg) {
|
|
83
155
|
err.status = err.status || 500;
|