soajs.controller 4.3.13 → 4.3.14
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.
|
@@ -292,11 +292,24 @@ Tracks user activity by notifying a service (typically URAC) of user access.
|
|
|
292
292
|
"serviceName": "urac", // Service to notify
|
|
293
293
|
"serviceVersion": "3", // Service version
|
|
294
294
|
"api": "/user/last/seen", // API endpoint
|
|
295
|
-
"network": "internal"
|
|
295
|
+
"network": "internal", // Optional network identifier
|
|
296
|
+
"include": { // Optional whitelist, omit for all requests
|
|
297
|
+
"connectspaces": true
|
|
298
|
+
},
|
|
299
|
+
"targets": [ // Optional extra APIs, same event
|
|
300
|
+
{
|
|
301
|
+
"serviceName": "authenticator",
|
|
302
|
+
"serviceVersion": "1",
|
|
303
|
+
"api": "/my/device/network",
|
|
304
|
+
"method": "put"
|
|
305
|
+
}
|
|
306
|
+
]
|
|
296
307
|
}
|
|
297
308
|
}
|
|
298
309
|
```
|
|
299
310
|
|
|
311
|
+
See `docs/middleware/lastSeen.md` for the `include` matching rules and the target fields.
|
|
312
|
+
|
|
300
313
|
| Field | Type | Default | Description |
|
|
301
314
|
|-------|------|---------|-------------|
|
|
302
315
|
| `active` | boolean | `false` | Enable/disable activity tracking |
|
|
@@ -69,7 +69,18 @@ Located in `registry.custom.gateway.value.lastSeen`:
|
|
|
69
69
|
"serviceName": "urac", // Target service
|
|
70
70
|
"serviceVersion": "3", // Service version
|
|
71
71
|
"api": "/user/last/seen", // API endpoint
|
|
72
|
-
"network": "internal"
|
|
72
|
+
"network": "internal", // Optional network identifier
|
|
73
|
+
"include": { // Optional whitelist, omit to trigger on everything
|
|
74
|
+
"connectspaces": true
|
|
75
|
+
},
|
|
76
|
+
"targets": [ // Optional extra APIs notified on the same event
|
|
77
|
+
{
|
|
78
|
+
"serviceName": "authenticator",
|
|
79
|
+
"serviceVersion": "1",
|
|
80
|
+
"api": "/my/device/network",
|
|
81
|
+
"method": "put"
|
|
82
|
+
}
|
|
83
|
+
]
|
|
73
84
|
}
|
|
74
85
|
}
|
|
75
86
|
```
|
|
@@ -81,6 +92,40 @@ Located in `registry.custom.gateway.value.lastSeen`:
|
|
|
81
92
|
| `serviceVersion` | string | `"3"` | Service version |
|
|
82
93
|
| `api` | string | `"/user/last/seen"` | API endpoint |
|
|
83
94
|
| `network` | string | - | Network identifier in request body |
|
|
95
|
+
| `include` | object | - | Whitelist of services and APIs, omit to trigger on every request |
|
|
96
|
+
| `targets` | array | - | Extra APIs notified on the same event |
|
|
97
|
+
|
|
98
|
+
### include
|
|
99
|
+
|
|
100
|
+
Decides whether a request counts as activity. Omitting it triggers on every request.
|
|
101
|
+
|
|
102
|
+
| Form | Meaning |
|
|
103
|
+
|------|---------|
|
|
104
|
+
| service missing from `include` | never triggers |
|
|
105
|
+
| `"service": true` | every API, every method |
|
|
106
|
+
| `"service": {"apis": {"/path": true}}` | that API, every method |
|
|
107
|
+
| `"service": {"apis": {"/path": ["get","put"]}}` | that API, listed methods only |
|
|
108
|
+
| `"apis": {"*": ...}` | any API, checked before the exact paths |
|
|
109
|
+
|
|
110
|
+
API keys accept the service relative path (`/active`) or the full path
|
|
111
|
+
(`/connectspaces/active`), and support path params (`/user/:id`).
|
|
112
|
+
|
|
113
|
+
### targets
|
|
114
|
+
|
|
115
|
+
Extra APIs notified whenever the main one is. They are not filtered on their own, the
|
|
116
|
+
`include` above has already decided that the request counts as activity, so a target is
|
|
117
|
+
notified on exactly the same set of requests.
|
|
118
|
+
|
|
119
|
+
| Field | Type | Default | Description |
|
|
120
|
+
|-------|------|---------|-------------|
|
|
121
|
+
| `serviceName` | string | required | Service to notify, must be in the registry |
|
|
122
|
+
| `api` | string | required | API endpoint |
|
|
123
|
+
| `serviceVersion` | string | - | Service version, resolved by awareness when absent |
|
|
124
|
+
| `method` | string | `"post"` | HTTP method |
|
|
125
|
+
|
|
126
|
+
Each target receives the same body and the same `soajsinjectobj` header as the main
|
|
127
|
+
notification. A target that is down, unknown to the registry, or missing `serviceName`
|
|
128
|
+
or `api` is skipped without affecting the others.
|
|
84
129
|
|
|
85
130
|
## Request to URAC
|
|
86
131
|
|
package/mw/lastSeen/index.js
CHANGED
|
@@ -143,6 +143,56 @@ function isMethodMatch(methodConfig, method) {
|
|
|
143
143
|
return false;
|
|
144
144
|
}
|
|
145
145
|
|
|
146
|
+
/**
|
|
147
|
+
* Fire and forget the notification at one target.
|
|
148
|
+
*
|
|
149
|
+
* NOTE: the request is not awaited, a target that is down or slow must not affect
|
|
150
|
+
* the original request, which has already been handed to the next middleware.
|
|
151
|
+
*
|
|
152
|
+
* @param req
|
|
153
|
+
* @param lastSeen the custom registry block, carries the shared body
|
|
154
|
+
* @param item {name, version, api, method}
|
|
155
|
+
*/
|
|
156
|
+
function notifyTarget(req, lastSeen, item) {
|
|
157
|
+
//NOTE: getHost resolves against the registry, an unknown service would throw on
|
|
158
|
+
// the port lookup below and take the remaining targets down with it
|
|
159
|
+
if (!req.soajs.registry || !req.soajs.registry.services ||
|
|
160
|
+
!req.soajs.registry.services[item.name] || !req.soajs.registry.services[item.name].port) {
|
|
161
|
+
req.soajs.log.debug("lastSeen failed, service not in registry [" + item.name + "@" + item.version + " " + item.api + "]");
|
|
162
|
+
return;
|
|
163
|
+
}
|
|
164
|
+
req.soajs.awareness.getHost(item.name, item.version, (host) => {
|
|
165
|
+
if (!host) {
|
|
166
|
+
req.soajs.log.debug("lastSeen failed with no host for [" + item.name + "@" + item.version + " " + item.api + "]");
|
|
167
|
+
return;
|
|
168
|
+
}
|
|
169
|
+
item.host = host;
|
|
170
|
+
item.port = req.soajs.registry.services[item.name].port;
|
|
171
|
+
|
|
172
|
+
let options = {
|
|
173
|
+
"uri": "http://" + item.host + ":" + item.port + item.api,
|
|
174
|
+
"json": true,
|
|
175
|
+
"method": item.method || "POST"
|
|
176
|
+
};
|
|
177
|
+
if (lastSeen.network) {
|
|
178
|
+
options.body = { "network": lastSeen.network };
|
|
179
|
+
}
|
|
180
|
+
if (req.headers && req.headers.soajsinjectobj) {
|
|
181
|
+
options.headers = { "soajsinjectobj": req.headers.soajsinjectobj };
|
|
182
|
+
}
|
|
183
|
+
httpRequest(options)
|
|
184
|
+
.then(() => { })
|
|
185
|
+
.catch(({ error, body }) => {
|
|
186
|
+
if (error) {
|
|
187
|
+
req.soajs.log.debug("lastSeen failed for [" + item.name + "@" + item.version + " " + item.api + "]");
|
|
188
|
+
req.soajs.log.debug("lastSeen " + error.message);
|
|
189
|
+
} else {
|
|
190
|
+
req.soajs.log.debug("lastSeen ", body);
|
|
191
|
+
}
|
|
192
|
+
});
|
|
193
|
+
});
|
|
194
|
+
}
|
|
195
|
+
|
|
146
196
|
/**
|
|
147
197
|
*
|
|
148
198
|
* @param configuration
|
|
@@ -180,39 +230,29 @@ module.exports = () => {
|
|
|
180
230
|
if (uracObj) {
|
|
181
231
|
userId = uracObj._id;
|
|
182
232
|
}
|
|
183
|
-
if (userId) {
|
|
184
|
-
req.soajs.
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
options.headers = { "soajsinjectobj": req.headers.soajsinjectobj };
|
|
199
|
-
}
|
|
200
|
-
httpRequest(options)
|
|
201
|
-
.then(() => { })
|
|
202
|
-
.catch(({ error, body }) => {
|
|
203
|
-
if (error) {
|
|
204
|
-
req.soajs.log.debug("lastSeen failed for [" + item.name + "@" + item.version + " " + item.api + "]");
|
|
205
|
-
req.soajs.log.debug("lastSeen " + error.message);
|
|
206
|
-
} else {
|
|
207
|
-
req.soajs.log.debug("lastSeen ", body);
|
|
208
|
-
}
|
|
209
|
-
});
|
|
210
|
-
} else {
|
|
211
|
-
req.soajs.log.debug("lastSeen failed with no host for [" + item.name + "@" + item.version + " " + item.api + "]");
|
|
233
|
+
if (!userId) {
|
|
234
|
+
req.soajs.log.debug("lastSeen failed with no userId for [" + item.name + "@" + item.version + " " + item.api + "]");
|
|
235
|
+
return;
|
|
236
|
+
}
|
|
237
|
+
|
|
238
|
+
notifyTarget(req, lastSeen, item);
|
|
239
|
+
|
|
240
|
+
//NOTE: additional targets are notified on the same event, they are not
|
|
241
|
+
// filtered on their own, the include above already decided that this
|
|
242
|
+
// request counts as activity
|
|
243
|
+
if (Array.isArray(lastSeen.targets)) {
|
|
244
|
+
lastSeen.targets.forEach((target) => {
|
|
245
|
+
if (!target || !target.serviceName || !target.api) {
|
|
246
|
+
req.soajs.log.debug("lastSeen target skipped, serviceName and api are required");
|
|
247
|
+
return;
|
|
212
248
|
}
|
|
249
|
+
notifyTarget(req, lastSeen, {
|
|
250
|
+
"name": target.serviceName,
|
|
251
|
+
"version": target.serviceVersion || null,
|
|
252
|
+
"api": target.api,
|
|
253
|
+
"method": target.method
|
|
254
|
+
});
|
|
213
255
|
});
|
|
214
|
-
} else {
|
|
215
|
-
req.soajs.log.debug("lastSeen failed with no userId for [" + item.name + "@" + item.version + " " + item.api + "]");
|
|
216
256
|
}
|
|
217
257
|
}
|
|
218
258
|
}
|
package/package.json
CHANGED
|
@@ -1009,4 +1009,172 @@ describe("Unit test for: mw - lastSeen", () => {
|
|
|
1009
1009
|
|
|
1010
1010
|
});
|
|
1011
1011
|
|
|
1012
|
+
describe("extra targets tests", () => {
|
|
1013
|
+
|
|
1014
|
+
let buildReq = (lastSeen) => {
|
|
1015
|
+
return {
|
|
1016
|
+
method: "GET",
|
|
1017
|
+
getClientIP: () => {
|
|
1018
|
+
return "127.0.0.1";
|
|
1019
|
+
},
|
|
1020
|
+
soajs: {
|
|
1021
|
+
log: {
|
|
1022
|
+
debug: () => {
|
|
1023
|
+
},
|
|
1024
|
+
error: () => {
|
|
1025
|
+
}
|
|
1026
|
+
},
|
|
1027
|
+
uracDriver: {
|
|
1028
|
+
getProfile: () => {
|
|
1029
|
+
return { _id: "1111111111" };
|
|
1030
|
+
}
|
|
1031
|
+
},
|
|
1032
|
+
awareness: {
|
|
1033
|
+
getHost: (name, version, cb) => {
|
|
1034
|
+
return cb(name + ".fake");
|
|
1035
|
+
}
|
|
1036
|
+
},
|
|
1037
|
+
controller: {
|
|
1038
|
+
serviceParams: {
|
|
1039
|
+
name: "connectspaces",
|
|
1040
|
+
path: "/active",
|
|
1041
|
+
parsedUrl: { pathname: "/connectspaces/active" }
|
|
1042
|
+
}
|
|
1043
|
+
},
|
|
1044
|
+
registry: {
|
|
1045
|
+
services: {
|
|
1046
|
+
urac: { port: 4001 },
|
|
1047
|
+
authenticator: { port: 4002 }
|
|
1048
|
+
},
|
|
1049
|
+
custom: {
|
|
1050
|
+
gateway: {
|
|
1051
|
+
value: { lastSeen: lastSeen }
|
|
1052
|
+
}
|
|
1053
|
+
}
|
|
1054
|
+
}
|
|
1055
|
+
}
|
|
1056
|
+
};
|
|
1057
|
+
};
|
|
1058
|
+
|
|
1059
|
+
it("notifies the extra target alongside urac, honouring its method", (done) => {
|
|
1060
|
+
let req = buildReq({
|
|
1061
|
+
active: true,
|
|
1062
|
+
network: "YAYA",
|
|
1063
|
+
targets: [{
|
|
1064
|
+
serviceName: "authenticator",
|
|
1065
|
+
serviceVersion: "1",
|
|
1066
|
+
api: "/my/device/network",
|
|
1067
|
+
method: "put"
|
|
1068
|
+
}]
|
|
1069
|
+
});
|
|
1070
|
+
const uracScope = nock('http://urac.fake:4001')
|
|
1071
|
+
.post('/user/last/seen', "{\"network\":\"YAYA\"}")
|
|
1072
|
+
.reply(200, { result: true });
|
|
1073
|
+
const authScope = nock('http://authenticator.fake:4002')
|
|
1074
|
+
.put('/my/device/network', "{\"network\":\"YAYA\"}")
|
|
1075
|
+
.reply(200, { result: true });
|
|
1076
|
+
|
|
1077
|
+
let functionMw = mw({});
|
|
1078
|
+
functionMw(req, {}, (error) => {
|
|
1079
|
+
assert.ifError(error);
|
|
1080
|
+
});
|
|
1081
|
+
setTimeout(() => {
|
|
1082
|
+
assert.strictEqual(uracScope.isDone(), true, "urac was not notified");
|
|
1083
|
+
assert.strictEqual(authScope.isDone(), true, "the extra target was not notified");
|
|
1084
|
+
done();
|
|
1085
|
+
}, 100);
|
|
1086
|
+
});
|
|
1087
|
+
|
|
1088
|
+
it("defaults the extra target to post when no method is set", (done) => {
|
|
1089
|
+
let req = buildReq({
|
|
1090
|
+
active: true,
|
|
1091
|
+
targets: [{
|
|
1092
|
+
serviceName: "authenticator",
|
|
1093
|
+
serviceVersion: "1",
|
|
1094
|
+
api: "/my/device/network"
|
|
1095
|
+
}]
|
|
1096
|
+
});
|
|
1097
|
+
nock('http://urac.fake:4001').post('/user/last/seen').reply(200, { result: true });
|
|
1098
|
+
const authScope = nock('http://authenticator.fake:4002')
|
|
1099
|
+
.post('/my/device/network')
|
|
1100
|
+
.reply(200, { result: true });
|
|
1101
|
+
|
|
1102
|
+
let functionMw = mw({});
|
|
1103
|
+
functionMw(req, {}, () => {
|
|
1104
|
+
});
|
|
1105
|
+
setTimeout(() => {
|
|
1106
|
+
assert.strictEqual(authScope.isDone(), true, "the extra target was not posted to");
|
|
1107
|
+
done();
|
|
1108
|
+
}, 100);
|
|
1109
|
+
});
|
|
1110
|
+
|
|
1111
|
+
it("still notifies urac when a target names a service that is not in the registry", (done) => {
|
|
1112
|
+
let req = buildReq({
|
|
1113
|
+
active: true,
|
|
1114
|
+
targets: [{
|
|
1115
|
+
serviceName: "doesnotexist",
|
|
1116
|
+
serviceVersion: "1",
|
|
1117
|
+
api: "/nope"
|
|
1118
|
+
}]
|
|
1119
|
+
});
|
|
1120
|
+
const uracScope = nock('http://urac.fake:4001')
|
|
1121
|
+
.post('/user/last/seen')
|
|
1122
|
+
.reply(200, { result: true });
|
|
1123
|
+
|
|
1124
|
+
let functionMw = mw({});
|
|
1125
|
+
assert.doesNotThrow(() => {
|
|
1126
|
+
functionMw(req, {}, () => {
|
|
1127
|
+
});
|
|
1128
|
+
});
|
|
1129
|
+
setTimeout(() => {
|
|
1130
|
+
assert.strictEqual(uracScope.isDone(), true, "urac was not notified");
|
|
1131
|
+
done();
|
|
1132
|
+
}, 100);
|
|
1133
|
+
});
|
|
1134
|
+
|
|
1135
|
+
it("skips a malformed target and still notifies urac", (done) => {
|
|
1136
|
+
let req = buildReq({
|
|
1137
|
+
active: true,
|
|
1138
|
+
targets: [{ serviceVersion: "1" }, null]
|
|
1139
|
+
});
|
|
1140
|
+
const uracScope = nock('http://urac.fake:4001')
|
|
1141
|
+
.post('/user/last/seen')
|
|
1142
|
+
.reply(200, { result: true });
|
|
1143
|
+
|
|
1144
|
+
let functionMw = mw({});
|
|
1145
|
+
functionMw(req, {}, () => {
|
|
1146
|
+
});
|
|
1147
|
+
setTimeout(() => {
|
|
1148
|
+
assert.strictEqual(uracScope.isDone(), true, "urac was not notified");
|
|
1149
|
+
done();
|
|
1150
|
+
}, 100);
|
|
1151
|
+
});
|
|
1152
|
+
|
|
1153
|
+
it("does not notify any target when the include filter excludes the request", (done) => {
|
|
1154
|
+
let req = buildReq({
|
|
1155
|
+
active: true,
|
|
1156
|
+
include: { "someotherservice": true },
|
|
1157
|
+
targets: [{
|
|
1158
|
+
serviceName: "authenticator",
|
|
1159
|
+
serviceVersion: "1",
|
|
1160
|
+
api: "/my/device/network",
|
|
1161
|
+
method: "put"
|
|
1162
|
+
}]
|
|
1163
|
+
});
|
|
1164
|
+
const uracScope = nock('http://urac.fake:4001').post('/user/last/seen').reply(200, {});
|
|
1165
|
+
const authScope = nock('http://authenticator.fake:4002').put('/my/device/network').reply(200, {});
|
|
1166
|
+
|
|
1167
|
+
let functionMw = mw({});
|
|
1168
|
+
functionMw(req, {}, () => {
|
|
1169
|
+
});
|
|
1170
|
+
setTimeout(() => {
|
|
1171
|
+
assert.strictEqual(uracScope.isDone(), false, "urac should have been skipped");
|
|
1172
|
+
assert.strictEqual(authScope.isDone(), false, "the extra target should have been skipped");
|
|
1173
|
+
nock.cleanAll();
|
|
1174
|
+
done();
|
|
1175
|
+
}, 100);
|
|
1176
|
+
});
|
|
1177
|
+
|
|
1178
|
+
});
|
|
1179
|
+
|
|
1012
1180
|
});
|