soajs.controller 4.3.6 → 4.3.9

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.
@@ -27,6 +27,8 @@ registry.custom
27
27
  │ │ └── [service] # Service-specific settings
28
28
  │ ├── maintenanceMode # Gateway maintenance mode
29
29
  │ ├── lastSeen # User activity tracking
30
+ │ ├── oauth
31
+ │ │ └── deviceIdCheck # Turn the deviceId check off
30
32
  │ └── gotoService
31
33
  │ ├── monitor # Request monitoring config
32
34
  │ └── renewReqMonitorOff # Disable timeout renewal
@@ -448,6 +450,37 @@ Whitelists specific APIs that can be accessed when a user is PIN-locked (during
448
450
 
449
451
  ---
450
452
 
453
+ ## 13. OAuth Device ID Check
454
+
455
+ **Path:** `registry.custom.gateway.value.oauth.deviceIdCheck`
456
+
457
+ **Middleware:** `mw/oauth/index.js`
458
+
459
+ Turns off the deviceId check done when authorising an access token. The check matches
460
+ `user.deviceId` on the access token record against the `device-id` header on every
461
+ private API call, and denies with error 156 on mismatch.
462
+
463
+ ```javascript
464
+ {
465
+ "oauth": {
466
+ "deviceIdCheck": false // Turn off the deviceId check
467
+ }
468
+ }
469
+ ```
470
+
471
+ | Field | Type | Default | Description |
472
+ |-------|------|---------|-------------|
473
+ | `deviceIdCheck` | boolean | `true` | Set to `false` to turn the deviceId check off |
474
+
475
+ Only the boolean `false` turns the check off. Any other value, and a missing or
476
+ malformed entry, leaves it on.
477
+
478
+ Note this is a `gateway` entry, not an `oauth` one. The `registry.custom.oauth.value.*`
479
+ entries above are consumed by `mw/mt/utils.js` and `mw/gotoService/roaming.js`, this one
480
+ is read by the oauth middleware itself.
481
+
482
+ ---
483
+
451
484
  ## Complete Configuration Example
452
485
 
453
486
  ```javascript
@@ -514,6 +547,9 @@ Whitelists specific APIs that can be accessed when a user is PIN-locked (during
514
547
  "api": "/user/last/seen",
515
548
  "network": "production"
516
549
  },
550
+ "oauth": {
551
+ "deviceIdCheck": false
552
+ },
517
553
  "gotoService": {
518
554
  "monitor": {
519
555
  "blacklist": ["soamonitor", "urac"]
@@ -565,6 +601,7 @@ Custom registry configurations are processed in this order:
565
601
  | 4 | gotoService/preRedirect | `gotoService.renewReqMonitorOff` |
566
602
  | 5 | gotoService/redirectToService | `gotoService.monitor` |
567
603
  | 6 | mt (security checks) | `mt.whitelist`, `oauth.value.pinWrapper`, `oauth.value.pinWhitelist` |
604
+ | 6a | oauth (called from mt) | `oauth.deviceIdCheck` |
568
605
  | 7 | idempotency | `idempotency.model`, `idempotency.services.[name]` |
569
606
  | 8 | traffic | `traffic.model`, `traffic.throttling` |
570
607
  | 9 | cache | `cache.model`, `cache.defaultTTL`, `cache.services.[name]` |
@@ -59,6 +59,7 @@ The Idempotency middleware prevents duplicate write operations by tracking reque
59
59
  {
60
60
  "idempotency": {
61
61
  "model": "memory", // Storage: "memory" or "mongo"
62
+ "enforce": false, // Global default for enforcement (optional)
62
63
  "services": { // Service configurations
63
64
  "av": { // Service name
64
65
  "enabled": true, // Enable for this service
@@ -71,6 +72,7 @@ The Idempotency middleware prevents duplicate write operations by tracking reque
71
72
  },
72
73
  "payment": {
73
74
  "enabled": true,
75
+ "enforce": true, // Require Idempotency-Key on matched APIs
74
76
  "ttl": 120000,
75
77
  "apis": [
76
78
  "POST /charge",
@@ -85,10 +87,18 @@ The Idempotency middleware prevents duplicate write operations by tracking reque
85
87
  | Field | Type | Default | Description |
86
88
  |-------|------|---------|-------------|
87
89
  | `model` | string | `"memory"` | Storage backend: `"memory"` or `"mongo"` |
90
+ | `enforce` | boolean | `false` | Global default enforcement (overridden per service) |
88
91
  | `services.[name].enabled` | boolean | `false` | Enable idempotency for this service |
92
+ | `services.[name].enforce` | boolean | *(global)* | Require `Idempotency-Key` on matched APIs; overrides the global `enforce`. If unset, falls back to the global default |
89
93
  | `services.[name].ttl` | number | `60000` | Key expiration time in milliseconds |
90
94
  | `services.[name].apis` | string[] | `[]` | API patterns to protect (e.g., `"POST /path"`) |
91
95
 
96
+ ## Enforcement
97
+
98
+ By default, requests without an `Idempotency-Key` header pass through untouched (backwards compatible). When `enforce` is enabled, a matched, non-GET API **requires** the header — a missing key returns **428 Precondition Required** (code `182`).
99
+
100
+ Resolution order: `services.[name].enforce` takes precedence when set to a boolean; otherwise the top-level `enforce` applies. Enforcement never affects GET requests, unconfigured/disabled services, or APIs outside the configured `apis` list.
101
+
92
102
  ## API Pattern Matching
93
103
 
94
104
  APIs are matched using `METHOD /path` format with support for path parameters:
@@ -181,6 +191,26 @@ Content-Type: application/json
181
191
  }
182
192
  ```
183
193
 
194
+ ### Enforced (Missing Key)
195
+
196
+ Returned when the matched API has enforcement enabled and no `Idempotency-Key` header is provided.
197
+
198
+ ```
199
+ HTTP/1.1 428 Precondition Required
200
+ Content-Type: application/json
201
+
202
+ {
203
+ "result": false,
204
+ "errors": {
205
+ "codes": [182],
206
+ "details": [{
207
+ "code": 182,
208
+ "message": "Idempotency-Key header is required for this API."
209
+ }]
210
+ }
211
+ }
212
+ ```
213
+
184
214
  ## Key Structure
185
215
 
186
216
  ```javascript
@@ -198,6 +228,7 @@ Keys are isolated per tenant to prevent cross-tenant collisions.
198
228
  |------|-------------|-------------|
199
229
  | 180 | 400 | Invalid Idempotency-Key format (not UUID v4) |
200
230
  | 181 | 409 | Request with this key is still being processed |
231
+ | 182 | 428 | Idempotency-Key header required (enforcement enabled) |
201
232
 
202
233
  ## Usage Notes
203
234
 
@@ -60,9 +60,53 @@ Uses oauth2-server library for token validation.
60
60
  │ bearerToken: user, │
61
61
  │ type: 2 │
62
62
  │ } │
63
- └─────────────────────────┘
63
+ └────────┬────────────────┘
64
+ │
65
+ ▼
66
+ ┌─────────────────────────┐
67
+ │ deviceId check │ token user.deviceId vs device-id header
68
+ └────────┬────────────────┘
69
+ │
70
+ ├─── Mismatch ──▶ Error 156
71
+ │
72
+ ▼
73
+ next()
64
74
  ```
65
75
 
76
+ #### deviceId check
77
+
78
+ The access token record carries `user.deviceId`, set at login from the `device-id`
79
+ header and carried forward across refreshes. It is matched against the `device-id`
80
+ header on every private API call. The check runs on the record `authorise()` already
81
+ fetched, so it costs no extra database call.
82
+
83
+ The agent cannot be used for this, mobile sends the build number in the user-agent
84
+ (ie: `democav/142 CFNetwork/3826.400.120 Darwin/24.3.0`) which changes on every build.
85
+
86
+ Tokens with no `deviceId` are not checked. This covers tokens created before deviceId
87
+ was introduced and clients that do not send the header at all, both get checked once
88
+ the client logs in again with the header. A client that sends `device-id` on login but
89
+ omits it on API calls is denied, the header has to be sent consistently.
90
+
91
+ This check is only available for type 2, a type 0 JWT carries no stored token record.
92
+
93
+ ##### Turning it off
94
+
95
+ The check is on by default. To turn it off for an environment, set `deviceIdCheck` to
96
+ `false` under the `gateway` custom registry entry:
97
+
98
+ ```json
99
+ {
100
+ "oauth": {
101
+ "deviceIdCheck": false
102
+ }
103
+ }
104
+ ```
105
+
106
+ Read from `registry.custom.gateway.value.oauth.deviceIdCheck`. Only the boolean `false`
107
+ turns it off, any other value leaves it on, so a missing or malformed entry keeps the
108
+ check running.
109
+
66
110
  ### Type 0: JWT
67
111
 
68
112
  Uses jsonwebtoken library for JWT validation.
@@ -155,6 +199,7 @@ Default OAuth service configuration:
155
199
  | Code | Description |
156
200
  |------|-------------|
157
201
  | 143 | Invalid or missing JWT token |
202
+ | 156 | Device forbidden, the deviceId on the token does not match the device-id header |
158
203
  | OAuth2Error | Various OAuth2 errors (invalid_token, expired, etc.) |
159
204
 
160
205
  ## Properties Set
@@ -62,11 +62,9 @@ module.exports = function (configuration) {
62
62
  return (req, res, next) => {
63
63
  const idempotencyKey = req.headers['idempotency-key'];
64
64
 
65
- if (!idempotencyKey) {
66
- return next();
67
- }
68
-
69
- if (!isValidUUIDv4(idempotencyKey)) {
65
+ // Validate format only when a key was actually provided. The decision on a
66
+ // missing key is deferred until we know whether the target API enforces it.
67
+ if (idempotencyKey && !isValidUUIDv4(idempotencyKey)) {
70
68
  req.soajs.controllerResponse({
71
69
  'status': 400,
72
70
  'code': 180,
@@ -103,6 +101,22 @@ module.exports = function (configuration) {
103
101
  return next();
104
102
  }
105
103
 
104
+ // Request targets an idempotency-enabled and matched API. Resolve enforcement:
105
+ // service-level 'enforce' overrides the global default.
106
+ const enforce = (typeof serviceConfig.enforce === 'boolean') ? serviceConfig.enforce : (idempotencyConfig.enforce === true);
107
+
108
+ if (!idempotencyKey) {
109
+ if (enforce) {
110
+ req.soajs.controllerResponse({
111
+ 'status': 428,
112
+ 'code': 182,
113
+ 'msg': 'Idempotency-Key header is required for this API.'
114
+ });
115
+ return;
116
+ }
117
+ return next();
118
+ }
119
+
106
120
  const modelType = idempotencyConfig.model || 'memory';
107
121
  if (!model[modelType]) {
108
122
  configuration.log.warn('Idempotency: Unknown model [' + modelType + ']. It can only be [memory || mongo]');
package/mw/oauth/index.js CHANGED
@@ -32,9 +32,57 @@ module.exports = (configuration) => {
32
32
  });
33
33
 
34
34
  let jwt = require('jsonwebtoken');
35
-
35
+
36
+ /**
37
+ * The deviceId check is on unless it is explicitly turned off at the custom registry.
38
+ * Anything other than false leaves it on.
39
+ *
40
+ * req.soajs.registry.custom.gateway.value.oauth
41
+ * {
42
+ "oauth": {
43
+ "deviceIdCheck": false
44
+ }
45
+ }
46
+ *
47
+ * @param req
48
+ * @returns {boolean} true when the check is turned on
49
+ */
50
+ let deviceIdCheckOn = (req) => {
51
+ if (req.soajs.registry &&
52
+ req.soajs.registry.custom &&
53
+ req.soajs.registry.custom.gateway &&
54
+ req.soajs.registry.custom.gateway.value &&
55
+ req.soajs.registry.custom.gateway.value.oauth &&
56
+ req.soajs.registry.custom.gateway.value.oauth.deviceIdCheck === false) {
57
+ return false;
58
+ }
59
+ return true;
60
+ };
61
+
62
+ /**
63
+ * Matches the deviceId on the access token record against the device-id header.
64
+ *
65
+ * NOTE: we cannot check for agent, mobile is sending the build number
66
+ * (ie: "democav/142 CFNetwork/3826.400.120 Darwin/24.3.0") which changes on every build.
67
+ * we check deviceId instead. tokens with no deviceId (created before deviceId was
68
+ * introduced, or by a client that does not send the header) are not checked, they get
69
+ * checked once the client logs in again.
70
+ *
71
+ * @param req
72
+ * @returns {boolean} true when the request is allowed to proceed
73
+ */
74
+ let deviceIdMatch = (req) => {
75
+ if (!deviceIdCheckOn(req)) {
76
+ return true;
77
+ }
78
+ if (!req.oauth || !req.oauth.bearerToken || !req.oauth.bearerToken.user || !req.oauth.bearerToken.user.deviceId) {
79
+ return true;
80
+ }
81
+ return req.oauth.bearerToken.user.deviceId === req.get('device-id');
82
+ };
83
+
36
84
  return (req, res, next) => {
37
-
85
+
38
86
  let oauthType = 2;
39
87
  let tenantOauth = req.soajs.tenantOauth;
40
88
  if (tenantOauth && Object.hasOwnProperty.call(tenantOauth, 'type')) {
@@ -45,7 +93,18 @@ module.exports = (configuration) => {
45
93
 
46
94
  //0=oauth0, 2=oauth2
47
95
  if (2 === oauthType) {
48
- oauthObj.authorise()(req, res, next);
96
+ //NOTE: authorise() sets req.oauth.bearerToken to the token record it fetched from the
97
+ // model, the deviceId check is done on it here to avoid fetching the token again.
98
+ oauthObj.authorise()(req, res, (error) => {
99
+ if (error) {
100
+ return next(error);
101
+ }
102
+ if (!deviceIdMatch(req)) {
103
+ req.soajs.log.debug("Access denied, deviceId mismatch [deviceId: " + req.get('device-id') + "]");
104
+ return next(156);
105
+ }
106
+ return next();
107
+ });
49
108
  } else {
50
109
  let algorithms = req.soajs.registry.serviceConfig.oauth.algorithms || ["HS256"];
51
110
  let audience = req.soajs.registry.serviceConfig.oauth.audience || "";
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.6",
4
+ "version": "4.3.9",
5
5
  "author": {
6
6
  "name": "soajs team",
7
7
  "email": "team@soajs.org"
@@ -29,7 +29,7 @@
29
29
  "oauth2-server": "2.4.1",
30
30
  "path-to-regexp": "8.4.0",
31
31
  "soajs.core.libs": "1.2.2",
32
- "soajs.core.modules": "5.2.19",
32
+ "soajs.core.modules": "5.2.20",
33
33
  "soajs.urac.driver": "3.2.15",
34
34
  "useragent": "2.3.0"
35
35
  },
@@ -30,6 +30,7 @@ describe("Unit test for: mw - idempotency", () => {
30
30
  it("should call next() when no Idempotency-Key header", (done) => {
31
31
  let req = {
32
32
  headers: {},
33
+ method: 'POST',
33
34
  soajs: {
34
35
  tenant: { id: '123' },
35
36
  controller: { serviceParams: { name: 'test', serviceInfo: ['', 'test', 'test'] } },
@@ -313,6 +314,244 @@ describe("Unit test for: mw - idempotency", () => {
313
314
  });
314
315
  });
315
316
 
317
+ describe("enforcement", () => {
318
+ it("should return 428 when enforced and no key on matched API", (done) => {
319
+ let responseData = null;
320
+ let req = {
321
+ headers: {},
322
+ method: 'POST',
323
+ soajs: {
324
+ tenant: { id: '123' },
325
+ controller: { serviceParams: { name: 'test', serviceInfo: ['', 'test', 'test'] } },
326
+ registry: {
327
+ custom: {
328
+ gateway: {
329
+ value: {
330
+ idempotency: {
331
+ services: {
332
+ test: { enabled: true, enforce: true, ttl: 60000, apis: ['POST /test'] }
333
+ }
334
+ }
335
+ }
336
+ }
337
+ }
338
+ },
339
+ controllerResponse: (data) => {
340
+ responseData = data;
341
+ }
342
+ }
343
+ };
344
+ let res = {};
345
+ let functionMw = mw(mockConfig);
346
+ functionMw(req, res, () => {
347
+ assert.fail('Should not call next');
348
+ });
349
+ setTimeout(() => {
350
+ assert.notStrictEqual(responseData, null);
351
+ assert.strictEqual(responseData.status, 428);
352
+ assert.strictEqual(responseData.code, 182);
353
+ done();
354
+ }, 10);
355
+ });
356
+
357
+ it("should call next() when enforced and key is present", (done) => {
358
+ let req = {
359
+ headers: { 'idempotency-key': '550e8400-e29b-41d4-a716-446655440010' },
360
+ method: 'POST',
361
+ soajs: {
362
+ tenant: { id: 'tenant-enforce' },
363
+ controller: { serviceParams: { name: 'test', serviceInfo: ['', 'test', 'test'] } },
364
+ registry: {
365
+ custom: {
366
+ gateway: {
367
+ value: {
368
+ idempotency: {
369
+ model: 'memory',
370
+ services: {
371
+ test: { enabled: true, enforce: true, ttl: 60000, apis: ['POST /test'] }
372
+ }
373
+ }
374
+ }
375
+ }
376
+ }
377
+ }
378
+ }
379
+ };
380
+ let res = {
381
+ writeHead: function() { return this; },
382
+ write: function() { return true; },
383
+ end: function() {},
384
+ on: function() {}
385
+ };
386
+ let functionMw = mw(mockConfig);
387
+ functionMw(req, res, (error) => {
388
+ assert.ifError(error);
389
+ done();
390
+ });
391
+ });
392
+
393
+ it("should call next() when not enforced and no key (backwards compatible)", (done) => {
394
+ let req = {
395
+ headers: {},
396
+ method: 'POST',
397
+ soajs: {
398
+ tenant: { id: '123' },
399
+ controller: { serviceParams: { name: 'test', serviceInfo: ['', 'test', 'test'] } },
400
+ registry: {
401
+ custom: {
402
+ gateway: {
403
+ value: {
404
+ idempotency: {
405
+ services: {
406
+ test: { enabled: true, ttl: 60000, apis: ['POST /test'] }
407
+ }
408
+ }
409
+ }
410
+ }
411
+ }
412
+ }
413
+ }
414
+ };
415
+ let res = {};
416
+ let functionMw = mw(mockConfig);
417
+ functionMw(req, res, (error) => {
418
+ assert.ifError(error);
419
+ done();
420
+ });
421
+ });
422
+
423
+ it("should return 428 when global enforce is set and service has no override", (done) => {
424
+ let responseData = null;
425
+ let req = {
426
+ headers: {},
427
+ method: 'POST',
428
+ soajs: {
429
+ tenant: { id: '123' },
430
+ controller: { serviceParams: { name: 'test', serviceInfo: ['', 'test', 'test'] } },
431
+ registry: {
432
+ custom: {
433
+ gateway: {
434
+ value: {
435
+ idempotency: {
436
+ enforce: true,
437
+ services: {
438
+ test: { enabled: true, ttl: 60000, apis: ['POST /test'] }
439
+ }
440
+ }
441
+ }
442
+ }
443
+ }
444
+ },
445
+ controllerResponse: (data) => {
446
+ responseData = data;
447
+ }
448
+ }
449
+ };
450
+ let res = {};
451
+ let functionMw = mw(mockConfig);
452
+ functionMw(req, res, () => {
453
+ assert.fail('Should not call next');
454
+ });
455
+ setTimeout(() => {
456
+ assert.notStrictEqual(responseData, null);
457
+ assert.strictEqual(responseData.status, 428);
458
+ assert.strictEqual(responseData.code, 182);
459
+ done();
460
+ }, 10);
461
+ });
462
+
463
+ it("should let service enforce=false override global enforce=true", (done) => {
464
+ let req = {
465
+ headers: {},
466
+ method: 'POST',
467
+ soajs: {
468
+ tenant: { id: '123' },
469
+ controller: { serviceParams: { name: 'test', serviceInfo: ['', 'test', 'test'] } },
470
+ registry: {
471
+ custom: {
472
+ gateway: {
473
+ value: {
474
+ idempotency: {
475
+ enforce: true,
476
+ services: {
477
+ test: { enabled: true, enforce: false, ttl: 60000, apis: ['POST /test'] }
478
+ }
479
+ }
480
+ }
481
+ }
482
+ }
483
+ }
484
+ }
485
+ };
486
+ let res = {};
487
+ let functionMw = mw(mockConfig);
488
+ functionMw(req, res, (error) => {
489
+ assert.ifError(error);
490
+ done();
491
+ });
492
+ });
493
+
494
+ it("should not enforce on GET requests", (done) => {
495
+ let req = {
496
+ headers: {},
497
+ method: 'GET',
498
+ soajs: {
499
+ tenant: { id: '123' },
500
+ controller: { serviceParams: { name: 'test', serviceInfo: ['', 'test', 'test'] } },
501
+ registry: {
502
+ custom: {
503
+ gateway: {
504
+ value: {
505
+ idempotency: {
506
+ services: {
507
+ test: { enabled: true, enforce: true, ttl: 60000, apis: ['POST /test'] }
508
+ }
509
+ }
510
+ }
511
+ }
512
+ }
513
+ }
514
+ }
515
+ };
516
+ let res = {};
517
+ let functionMw = mw(mockConfig);
518
+ functionMw(req, res, (error) => {
519
+ assert.ifError(error);
520
+ done();
521
+ });
522
+ });
523
+
524
+ it("should not enforce when API is not in the configured list", (done) => {
525
+ let req = {
526
+ headers: {},
527
+ method: 'POST',
528
+ soajs: {
529
+ tenant: { id: '123' },
530
+ controller: { serviceParams: { name: 'test', serviceInfo: ['', 'test', 'other'] } },
531
+ registry: {
532
+ custom: {
533
+ gateway: {
534
+ value: {
535
+ idempotency: {
536
+ services: {
537
+ test: { enabled: true, enforce: true, ttl: 60000, apis: ['POST /test'] }
538
+ }
539
+ }
540
+ }
541
+ }
542
+ }
543
+ }
544
+ }
545
+ };
546
+ let res = {};
547
+ let functionMw = mw(mockConfig);
548
+ functionMw(req, res, (error) => {
549
+ assert.ifError(error);
550
+ done();
551
+ });
552
+ });
553
+ });
554
+
316
555
  describe("UUID validation", () => {
317
556
  it("should accept valid UUID v4", (done) => {
318
557
  let req = {
@@ -99,3 +99,171 @@ describe("Unit test for: mw - oauth", () => {
99
99
  });
100
100
  });
101
101
  });
102
+
103
+ describe("Unit test for: mw - oauth deviceId check", () => {
104
+
105
+ // builds a configuration whose model returns an access token carrying the given deviceId
106
+ let buildConfiguration = (deviceId) => {
107
+ let user = {"id": "1"};
108
+ if (deviceId !== undefined) {
109
+ user.deviceId = deviceId;
110
+ }
111
+ return {
112
+ "soajs": {
113
+ "param": {}
114
+ },
115
+ "serviceConfig": {
116
+ "oauth": {
117
+ grants: ["password", "refresh_token"],
118
+ debug: false,
119
+ accessTokenLifetime: 7200,
120
+ refreshTokenLifetime: 1209600
121
+ }
122
+ },
123
+ "model": {
124
+ "getAccessToken": (bearerToken, cb) => {
125
+ return cb(null, {
126
+ "token": bearerToken,
127
+ "expires": new Date(new Date().getTime() + 3600000),
128
+ "user": user
129
+ });
130
+ }
131
+ }
132
+ };
133
+ };
134
+
135
+ // oauthType 2 so that the access token is fetched from the model
136
+ let buildReq = (headerDeviceId) => {
137
+ return {
138
+ "soajs": {
139
+ "log": {
140
+ "debug": () => {
141
+ }
142
+ },
143
+ "tenantOauth": {
144
+ "type": 2
145
+ },
146
+ "servicesConfig": {},
147
+ "registry": {
148
+ "serviceConfig": {
149
+ "oauth": {
150
+ "type": 2
151
+ }
152
+ }
153
+ }
154
+ },
155
+ "query": {},
156
+ "body": {},
157
+ "get": (what) => {
158
+ if ('Authorization' === what) {
159
+ return "Bearer anAccessToken";
160
+ }
161
+ if ('device-id' === what) {
162
+ return headerDeviceId;
163
+ }
164
+ return undefined;
165
+ }
166
+ };
167
+ };
168
+ let res = {};
169
+
170
+ it("test oauth MW - deviceId matches", (done) => {
171
+ let functionMw = mw(buildConfiguration("2222222222"));
172
+ let req = buildReq("2222222222");
173
+ functionMw(req, res, (error) => {
174
+ assert.ifError(error);
175
+ assert.ok(req.oauth.bearerToken);
176
+ done();
177
+ });
178
+ });
179
+
180
+ it("test oauth MW - deviceId does not match", (done) => {
181
+ let functionMw = mw(buildConfiguration("3333333333"));
182
+ let req = buildReq("4444444444");
183
+ functionMw(req, res, (error) => {
184
+ assert.deepStrictEqual(error, 156);
185
+ done();
186
+ });
187
+ });
188
+
189
+ it("test oauth MW - deviceId on the token but no header", (done) => {
190
+ let functionMw = mw(buildConfiguration("3333333333"));
191
+ let req = buildReq(undefined);
192
+ functionMw(req, res, (error) => {
193
+ assert.deepStrictEqual(error, 156);
194
+ done();
195
+ });
196
+ });
197
+
198
+ it("test oauth MW - no deviceId on the token, check is skipped", (done) => {
199
+ let functionMw = mw(buildConfiguration(undefined));
200
+ let req = buildReq("5555555555");
201
+ functionMw(req, res, (error) => {
202
+ assert.ifError(error);
203
+ assert.ok(req.oauth.bearerToken);
204
+ done();
205
+ });
206
+ });
207
+
208
+ it("test oauth MW - null deviceId on the token, check is skipped", (done) => {
209
+ let functionMw = mw(buildConfiguration(null));
210
+ let req = buildReq("5555555555");
211
+ functionMw(req, res, (error) => {
212
+ assert.ifError(error);
213
+ assert.ok(req.oauth.bearerToken);
214
+ done();
215
+ });
216
+ });
217
+
218
+ // turns the check off at registry.custom.gateway.value.oauth
219
+ let turnCheckOff = (req, deviceIdCheck) => {
220
+ req.soajs.registry.custom = {
221
+ "gateway": {
222
+ "value": {
223
+ "oauth": {
224
+ "deviceIdCheck": deviceIdCheck
225
+ }
226
+ }
227
+ }
228
+ };
229
+ return req;
230
+ };
231
+
232
+ it("test oauth MW - deviceId mismatch but check turned off at registry", (done) => {
233
+ let functionMw = mw(buildConfiguration("3333333333"));
234
+ let req = turnCheckOff(buildReq("4444444444"), false);
235
+ functionMw(req, res, (error) => {
236
+ assert.ifError(error);
237
+ assert.ok(req.oauth.bearerToken);
238
+ done();
239
+ });
240
+ });
241
+
242
+ it("test oauth MW - deviceId mismatch and check explicitly turned on at registry", (done) => {
243
+ let functionMw = mw(buildConfiguration("3333333333"));
244
+ let req = turnCheckOff(buildReq("4444444444"), true);
245
+ functionMw(req, res, (error) => {
246
+ assert.deepStrictEqual(error, 156);
247
+ done();
248
+ });
249
+ });
250
+
251
+ it("test oauth MW - only false turns the check off, not a truthy value", (done) => {
252
+ let functionMw = mw(buildConfiguration("3333333333"));
253
+ let req = turnCheckOff(buildReq("4444444444"), "false");
254
+ functionMw(req, res, (error) => {
255
+ assert.deepStrictEqual(error, 156);
256
+ done();
257
+ });
258
+ });
259
+
260
+ it("test oauth MW - empty custom registry leaves the check on", (done) => {
261
+ let functionMw = mw(buildConfiguration("3333333333"));
262
+ let req = buildReq("4444444444");
263
+ req.soajs.registry.custom = {};
264
+ functionMw(req, res, (error) => {
265
+ assert.deepStrictEqual(error, 156);
266
+ done();
267
+ });
268
+ });
269
+ });