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.
@@ -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(JSON.stringify(awarenessCache));
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) {
@@ -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(JSON.stringify({ "url": req.url, "headers": req.headers }));
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(JSON.stringify({
32
- "serviceName": restServiceParams.name,
33
- "host": host,
34
- "version": restServiceParams.version,
35
- "url": restServiceParams.url,
36
- "port": restServiceParams.registry.port,
37
- "header": req.headers
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 = req.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(JSON.stringify({ "url": req.url, "headers": req.headers }));
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,7 +1,7 @@
1
1
  {
2
2
  "name": "soajs.controller",
3
3
  "description": "soajs multi tenant API gateway",
4
- "version": "4.3.10",
4
+ "version": "4.3.11",
5
5
  "author": {
6
6
  "name": "soajs team",
7
7
  "email": "team@soajs.org"
@@ -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
- soajs: {
23
- log: {
24
- error: (input) => {
25
- // console.error(input);
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
+ });
@@ -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.warn(JSON.stringify({ "url": req.url, "headers": req.headers }));
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;