scimgateway 5.4.3 → 5.4.4

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/README.md CHANGED
@@ -16,6 +16,7 @@ Validated through IdP's:
16
16
 
17
17
  Latest news:
18
18
 
19
+ - External JWKS (JSON Web Key Set) is now supported by JWT Authentication. These are public and typically frequent rotated by modern identity providers
19
20
  - [Azure Relay](https://learn.microsoft.com/en-us/azure/azure-relay/relay-what-is-it) is now supported for secure and hassle-free outbound communication — with just one minute of configuration
20
21
  - [ETag](https://datatracker.ietf.org/doc/html/rfc7644#section-3.14) is now supported
21
22
  - [Bulk Operations](https://datatracker.ietf.org/doc/html/rfc7644#section-3.7) is now supported
@@ -417,7 +418,7 @@ Definitions in `endpoint` object are customized according to our plugin code. Pl
417
418
 
418
419
  - **auth.bearerJwtAzure** - Array of one or more JWT used by Azure SyncFabric. **tenantIdGUID** must be set to Entra ID Tenant ID.
419
420
 
420
- - **auth.bearerJwt** - Array of one or more standard JWT objects. Using **secret** or **publicKey** for signature verification. publicKey should be set to the filename of public key or certificate pem-file located in `<package-root>\config\certs` or absolute path being used. Clear text secret will become encrypted when gateway is started. **options.issuer** is mandatory. Other options may also be included according to jsonwebtoken npm package definition.
421
+ - **auth.bearerJwt** - Array of one or more standard JWT objects. Using **secret**, **publicKey** or **wellKnownUri** for signature verification. publicKey should be set to the filename of public key or certificate pem-file located in `<package-root>\config\certs` or absolute path being used. Clear text secret will become encrypted when gateway is started. For JWKS (JSON Web Key Set), the **wellKnownUri** must be set to identity provider well-known URI which will be used for lookup the jwks_uri key. **options.issuer** should normally be set for validation when using secret or publicKey. Other options may also be included according to jsonwebtoken npm package definition.
421
422
 
422
423
  - **auth.bearerOAuth** - Array of one or more Client Credentials OAuth configuration objects. **`clientId`** and **`clientSecret`** are mandatory. clientSecret value will become encrypted when gateway is started. OAuth token request url is **/oauth/token** e.g. `http://localhost:8880/oauth/token`
423
424
 
@@ -816,7 +817,7 @@ const messageHandler = async (message: string) => {
816
817
  console.log(message)
817
818
  }
818
819
 
819
- async function startSSE() {
820
+ async function startup() {
820
821
  while (true) {
821
822
  try {
822
823
  const resp = await fetch(url, { headers });
@@ -847,7 +848,7 @@ async function startSSE() {
847
848
  }
848
849
  }
849
850
 
850
- startSSE()
851
+ startup()
851
852
  ```
852
853
 
853
854
  ### Configuration notes - Azure Relay
@@ -1466,7 +1467,29 @@ In code editor (e.g., Visual Studio Code), method details and documentation are
1466
1467
  MIT © [Jarle Elshaug](https://www.elshaug.xyz)
1467
1468
 
1468
1469
 
1469
- ## Change log
1470
+ ## Change log
1471
+
1472
+ ### v5.4.4
1473
+
1474
+ [Improved]
1475
+
1476
+ - External JWKS (JSON Web Key Set) is now supported by JWT Authentication. These are public and typically frequent rotated by modern identity providers
1477
+
1478
+ JKWS is enabled by setting scimgateway.auth.bearerJwt[].wellKnownUri to the identity provider's well-known URI
1479
+
1480
+ Keycloak example:
1481
+
1482
+ auth: {
1483
+ "bearerJwt": [
1484
+ {
1485
+ "wellKnownUri": "https://keycloak.example.com/realms/example-realm/.well-known/openid-configuration",
1486
+ "options": {
1487
+ ...
1488
+ },
1489
+ ...
1490
+ }
1491
+ ]
1492
+ }
1470
1493
 
1471
1494
  ### v5.4.3
1472
1495
 
package/bun.lock CHANGED
@@ -17,6 +17,7 @@
17
17
  "hyco-https": "^1.4.5",
18
18
  "is-in-subnet": "^4.0.1",
19
19
  "jsonwebtoken": "^9.0.2",
20
+ "jwk-to-pem": "^2.0.7",
20
21
  "ldapjs": "^3.0.7",
21
22
  "lokijs": "^1.5.12",
22
23
  "mongodb": "^6.16.0",
@@ -31,6 +32,7 @@
31
32
  "@types/bun": "latest",
32
33
  "@types/dot-object": "^2.1.6",
33
34
  "@types/jsonwebtoken": "^9.0.9",
35
+ "@types/jwk-to-pem": "^2.0.3",
34
36
  "@types/node": "latest",
35
37
  "@types/nodemailer": "^6.4.17",
36
38
  "@types/passport": "^1.0.17",
@@ -163,6 +165,8 @@
163
165
 
164
166
  "@types/jsonwebtoken": ["@types/jsonwebtoken@9.0.10", "", { "dependencies": { "@types/ms": "*", "@types/node": "*" } }, "sha512-asx5hIG9Qmf/1oStypjanR7iKTv0gXQ1Ov/jfrX6kS/EO0OFni8orbmGCn0672NHR3kXHwpAwR+B368ZGN/2rA=="],
165
167
 
168
+ "@types/jwk-to-pem": ["@types/jwk-to-pem@2.0.3", "", {}, "sha512-I/WFyFgk5GrNbkpmt14auGO3yFK1Wt4jXzkLuI+fDBNtO5ZI2rbymyGd6bKzfSBEuyRdM64ZUwxU1+eDcPSOEQ=="],
169
+
166
170
  "@types/ldapjs": ["@types/ldapjs@3.0.6", "", { "dependencies": { "@types/node": "*" } }, "sha512-E2Tn1ltJDYBsidOT9QG4engaQeQzRQ9aYNxVmjCkD33F7cIeLPgrRDXAYs0O35mK2YDU20c/+ZkNjeAPRGLM0Q=="],
167
171
 
168
172
  "@types/lokijs": ["@types/lokijs@1.5.14", "", {}, "sha512-4Fic47BX3Qxr8pd12KT6/T1XWU8dOlJBIp1jGoMbaDbiEvdv50rAii+B3z1b/J2pvMywcVP+DBPGP5/lgLOKGA=="],
@@ -235,6 +239,8 @@
235
239
 
236
240
  "argparse": ["argparse@2.0.1", "", {}, "sha512-8+9WqebbFzpX9OR+Wa6O29asIogeRMzcGtAINdpMHHyAg10f05aSFVBbcEqGf/PXw1EjAZ+q2/bEBg3DvurK3Q=="],
237
241
 
242
+ "asn1.js": ["asn1.js@5.4.1", "", { "dependencies": { "bn.js": "^4.0.0", "inherits": "^2.0.1", "minimalistic-assert": "^1.0.0", "safer-buffer": "^2.1.0" } }, "sha512-+I//4cYPccV8LdmBLiX8CYvf9Sp3vQsrqu2QNXRcrbiWvcx/UdlFiqUJJzxRQxgsZmvhXhn4cSKeSmoFjVdupA=="],
243
+
238
244
  "assert-plus": ["assert-plus@1.0.0", "", {}, "sha512-NfJ4UzBCcQGLDlQq7nHxH+tv3kyZ0hHQqF5BO6J7tNJeP5do1llPr8dZ8zHonfhAu0PHAdMkSo+8o0wxg9lZWw=="],
239
245
 
240
246
  "async": ["async@3.2.6", "", {}, "sha512-htCUDlxyyCLMgaM3xXg0C0LW2xqfuQ6p05pCEIsXuyQ+a1koYKTuBMzRNwmybfLgvJDMd0r1LTn4+E0Ti6C2AA=="],
@@ -253,10 +259,14 @@
253
259
 
254
260
  "bl": ["bl@6.1.0", "", { "dependencies": { "@types/readable-stream": "^4.0.0", "buffer": "^6.0.3", "inherits": "^2.0.4", "readable-stream": "^4.2.0" } }, "sha512-ClDyJGQkc8ZtzdAAbAwBmhMSpwN/sC9HA8jxdYm6nVUbCfZbe2mgza4qh7AuEYyEPB/c4Kznf9s66bnsKMQDjw=="],
255
261
 
262
+ "bn.js": ["bn.js@4.12.2", "", {}, "sha512-n4DSx829VRTRByMRGdjQ9iqsN0Bh4OolPsFnaZBLcbi8iXcB+kJ9s7EnRt4wILZNV3kPLHkRVfOc/HvhC3ovDw=="],
263
+
256
264
  "brace-expansion": ["brace-expansion@1.1.12", "", { "dependencies": { "balanced-match": "^1.0.0", "concat-map": "0.0.1" } }, "sha512-9T9UjW3r0UW5c1Q7GTwllptXwhvYmEzFhzMfZ9H7FQWt+uZePjZPjBP/W1ZEyZ1twGWom5/56TF4lPcqjnDHcg=="],
257
265
 
258
266
  "braces": ["braces@3.0.3", "", { "dependencies": { "fill-range": "^7.1.1" } }, "sha512-yQbXgO/OSZVD2IsiLlro+7Hf6Q18EJrKSEsdoMzKePKXct3gvD8oLcOQdIzGupr5Fj+EDe8gO/lxc1BzfMpxvA=="],
259
267
 
268
+ "brorand": ["brorand@1.1.0", "", {}, "sha512-cKV8tMCEpQs4hK/ik71d6LrPOnpkpGBR0wzxqr68g2m/LB2GxVYQroAjMJZRVM1Y4BCjCKc3vAamxSzOY2RP+w=="],
269
+
260
270
  "bson": ["bson@6.10.4", "", {}, "sha512-WIsKqkSC0ABoBJuT1LEX+2HEvNmNKKgnTAyd0fL8qzK4SH2i9NXg+t08YtdZp/V9IZ33cxe3iV4yM0qg8lMQng=="],
261
271
 
262
272
  "buffer": ["buffer@6.0.3", "", { "dependencies": { "base64-js": "^1.3.1", "ieee754": "^1.2.1" } }, "sha512-FTiCpNxtwiZZHEZbcbTIcZjERVICn9yq/pDFkTl95/AxzD1naBctN7YO68riM/gLSDY7sdrMby8hofADYuuqOA=="],
@@ -315,6 +325,8 @@
315
325
 
316
326
  "ecdsa-sig-formatter": ["ecdsa-sig-formatter@1.0.11", "", { "dependencies": { "safe-buffer": "^5.0.1" } }, "sha512-nagl3RYrbNv6kQkeJIpt6NJZy8twLB/2vtz6yN9Z4vRKHN4/QZJIEbqohALSgwKdnksuY3k5Addp5lg8sVoVcQ=="],
317
327
 
328
+ "elliptic": ["elliptic@6.6.1", "", { "dependencies": { "bn.js": "^4.11.9", "brorand": "^1.1.0", "hash.js": "^1.0.0", "hmac-drbg": "^1.0.1", "inherits": "^2.0.4", "minimalistic-assert": "^1.0.1", "minimalistic-crypto-utils": "^1.0.1" } }, "sha512-RaddvvMatK2LJHqFJ+YA4WysVN5Ita9E35botqIYspQ4TkRAlCicdzKOjlyv/1Za5RyTNn7di//eEV0uTAfe3g=="],
329
+
318
330
  "es-define-property": ["es-define-property@1.0.1", "", {}, "sha512-e3nRfgfUZ4rNGL232gUgX06QNyyez04KdjFrF+LTRoOXmrOgFKDg4BCdsjW8EnT69eqdYGmRpJwiPVYNrCaW3g=="],
319
331
 
320
332
  "es-errors": ["es-errors@1.3.0", "", {}, "sha512-Zf5H2Kxt2xjTvbJvP2ZWLEICxA6j+hAmMzIlypy4xcBg1vKVnx89Wy0GbS+kf5cwCVFFzdCFh2XSCFNULS6csw=="],
@@ -399,8 +411,12 @@
399
411
 
400
412
  "has-tostringtag": ["has-tostringtag@1.0.2", "", { "dependencies": { "has-symbols": "^1.0.3" } }, "sha512-NqADB8VjPFLM2V0VvHUewwwsw0ZWBaIdgo+ieHtK3hasLz4qeCRjYcqfB6AQrBggRKppKF8L52/VqdVsO47Dlw=="],
401
413
 
414
+ "hash.js": ["hash.js@1.1.7", "", { "dependencies": { "inherits": "^2.0.3", "minimalistic-assert": "^1.0.1" } }, "sha512-taOaskGt4z4SOANNseOviYDvjEJinIkRgmp7LbKP2YTTmVxWBl87s/uzK9r+44BclBSp2X7K1hqeNfz9JbBeXA=="],
415
+
402
416
  "hasown": ["hasown@2.0.2", "", { "dependencies": { "function-bind": "^1.1.2" } }, "sha512-0hJU9SCPvmMzIBdZFqNPXWa6dqh7WdH0cII9y+CyS8rG3nL48Bclra9HmKhVVUHyPWNH5Y7xDwAB7bfgSjkUMQ=="],
403
417
 
418
+ "hmac-drbg": ["hmac-drbg@1.0.1", "", { "dependencies": { "hash.js": "^1.0.3", "minimalistic-assert": "^1.0.0", "minimalistic-crypto-utils": "^1.0.1" } }, "sha512-Tti3gMqLdZfhOQY1Mzf/AanLiqh1WTiJgEj26ZuYQ9fbkLomzGchCws4FyrSd4VkpBfiNhaE1On+lOz894jvXg=="],
419
+
404
420
  "http-proxy-agent": ["http-proxy-agent@7.0.2", "", { "dependencies": { "agent-base": "^7.1.0", "debug": "^4.3.4" } }, "sha512-T1gkAiYYDWYx3V5Bmyu7HcfcvL7mUrTWiM6yOfa3PIphViJ/gFPbvidQ+veqSOHci/PxBcDabeUNCzpOODJZig=="],
405
421
 
406
422
  "https": ["https@1.0.0", "", {}, "sha512-4EC57ddXrkaF0x83Oj8sM6SLQHAWXw90Skqu2M4AEWENZ3F02dFJE/GARA8igO79tcgYqGrD7ae4f5L3um2lgg=="],
@@ -463,6 +479,8 @@
463
479
 
464
480
  "jwa": ["jwa@1.4.2", "", { "dependencies": { "buffer-equal-constant-time": "^1.0.1", "ecdsa-sig-formatter": "1.0.11", "safe-buffer": "^5.0.1" } }, "sha512-eeH5JO+21J78qMvTIDdBXidBd6nG2kZjg5Ohz/1fpa28Z4CcsWUzJ1ZZyFq/3z3N17aZy+ZuBoHljASbL1WfOw=="],
465
481
 
482
+ "jwk-to-pem": ["jwk-to-pem@2.0.7", "", { "dependencies": { "asn1.js": "^5.3.0", "elliptic": "^6.6.1", "safe-buffer": "^5.0.1" } }, "sha512-cSVphrmWr6reVchuKQZdfSs4U9c5Y4hwZggPoz6cbVnTpAVgGRpEuQng86IyqLeGZlhTh+c4MAreB6KbdQDKHQ=="],
483
+
466
484
  "jws": ["jws@3.2.2", "", { "dependencies": { "jwa": "^1.4.1", "safe-buffer": "^5.0.1" } }, "sha512-YHlZCB6lMTllWDtSPHz/ZXTsi8S00usEV6v1tjq8tOUZzw7DpSDWVXjXDre6ed1w/pd495ODpHZYSdkRTsa0HA=="],
467
485
 
468
486
  "keyv": ["keyv@4.5.4", "", { "dependencies": { "json-buffer": "3.0.1" } }, "sha512-oxVHkHR/EJf2CNXnWxRLW6mg7JyCCUcG0DtEGmL2ctUo1PNTin1PUil+r/+4r5MpVgC/fn1kjsx7mjSujKqIpw=="],
@@ -507,6 +525,10 @@
507
525
 
508
526
  "micromatch": ["micromatch@4.0.8", "", { "dependencies": { "braces": "^3.0.3", "picomatch": "^2.3.1" } }, "sha512-PXwfBhYu0hBCPw8Dn0E+WDYb7af3dSLVWKi3HGv84IdF4TyFoC0ysxFd0Goxw7nSv4T/PzEJQxsYsEiFCKo2BA=="],
509
527
 
528
+ "minimalistic-assert": ["minimalistic-assert@1.0.1", "", {}, "sha512-UtJcAD4yEaGtjPezWuO9wC4nwUnVH/8/Im3yEHQP4b67cXlD/Qr9hdITCU1xDbSEXg2XKNaP8jsReV7vQd00/A=="],
529
+
530
+ "minimalistic-crypto-utils": ["minimalistic-crypto-utils@1.0.1", "", {}, "sha512-JIYlbt6g8i5jKfJ3xz7rF0LXmv2TkDxBLUkiBeZ7bAx4GnnNMr8xFpGnOxn6GhTEHx3SjRrZEoU+j04prX1ktg=="],
531
+
510
532
  "minimatch": ["minimatch@3.1.2", "", { "dependencies": { "brace-expansion": "^1.1.7" } }, "sha512-J7p63hRiAjw1NDEww1W7i37+ByIrOWO5XQQAzZ3VOcL0PNybwpfmV/N05zFAzwQ9USyEcX6t3UO+K5aqBQOIHw=="],
511
533
 
512
534
  "minimist": ["minimist@1.2.8", "", {}, "sha512-2yyAR8qBkN3YuheJanUpWC5U3bb5osDywNB8RzDVlDwDHbocAJveqqj1u8+SVD7jkWT4yvsHCpWqqWqAxb0zCA=="],
@@ -44,6 +44,7 @@
44
44
  {
45
45
  "secret": null,
46
46
  "publicKey": null,
47
+ "wellKnownUri": null,
47
48
  "options": {
48
49
  "issuer": null
49
50
  },
@@ -44,6 +44,7 @@
44
44
  {
45
45
  "secret": null,
46
46
  "publicKey": null,
47
+ "wellKnownUri": null,
47
48
  "options": {
48
49
  "issuer": null
49
50
  },
@@ -44,6 +44,7 @@
44
44
  {
45
45
  "secret": null,
46
46
  "publicKey": null,
47
+ "wellKnownUri": null,
47
48
  "options": {
48
49
  "issuer": null
49
50
  },
@@ -44,6 +44,7 @@
44
44
  {
45
45
  "secret": null,
46
46
  "publicKey": null,
47
+ "wellKnownUri": null,
47
48
  "options": {
48
49
  "issuer": null
49
50
  },
@@ -50,6 +50,7 @@
50
50
  {
51
51
  "secret": null,
52
52
  "publicKey": null,
53
+ "wellKnownUri": null,
53
54
  "options": {
54
55
  "issuer": null
55
56
  },
@@ -44,6 +44,7 @@
44
44
  {
45
45
  "secret": null,
46
46
  "publicKey": null,
47
+ "wellKnownUri": null,
47
48
  "options": {
48
49
  "issuer": null
49
50
  },
@@ -44,6 +44,7 @@
44
44
  {
45
45
  "secret": null,
46
46
  "publicKey": null,
47
+ "wellKnownUri": null,
47
48
  "options": {
48
49
  "issuer": null
49
50
  },
@@ -44,6 +44,7 @@
44
44
  {
45
45
  "secret": null,
46
46
  "publicKey": null,
47
+ "wellKnownUri": null,
47
48
  "options": {
48
49
  "issuer": null
49
50
  },
@@ -44,6 +44,7 @@
44
44
  {
45
45
  "secret": null,
46
46
  "publicKey": null,
47
+ "wellKnownUri": null,
47
48
  "options": {
48
49
  "issuer": null
49
50
  },
@@ -21,6 +21,7 @@ import dot from 'dot-object'
21
21
  import nodemailer from 'nodemailer'
22
22
  import fs from 'node:fs'
23
23
  import path from 'node:path'
24
+ import jwkToPem from 'jwk-to-pem'
24
25
  import * as jwt from 'jsonwebtoken'
25
26
  import * as utils from './utils.ts'
26
27
  import * as utilsScim from './utils-scim.ts'
@@ -37,7 +38,6 @@ export class ScimGateway {
37
38
  private getMemberOf: any
38
39
  private getAppRoles: any
39
40
  private pub: any
40
- private Nonce = new utils.TimerMapCache(2000)
41
41
  // @ts-expect-error: has no initializer
42
42
  private helperRest: HelperRest
43
43
  /** pluginName is the name of plugin e.g., plugin-loki */
@@ -670,37 +670,78 @@ export class ScimGateway {
670
670
  })
671
671
  }
672
672
 
673
- const jwtVerify = async (baseEntity: string, method: string, el: Record<string, any>, authToken: string) => { // used by bearerJwt
674
- return await new Promise((resolve) => {
675
- jwt.verify(authToken, (el.secret) ? el.secret : el.publicKeyContent, el.options, (err) => {
676
- if (err != null) resolve(false)
677
- else {
678
- if (el.baseEntities) {
679
- if (Array.isArray(el.baseEntities) && el.baseEntities.length > 0) {
680
- if (!baseEntity) return resolve(false)
681
- if (!el.baseEntities.includes(baseEntity)) return resolve(false)
682
- }
683
- }
684
- if (el.readOnly === true && method !== 'GET') return resolve(false)
685
- resolve(true) // authorization OK
673
+ const getJwksPemKey = async (kid: string, wellKnownUri: string): Promise<Array<any>> => { // retrieves "JSON Web Key Set" from well-known jwks_uri and returns the public key that corresponds with the access token kid value
674
+ if (!this.helperRest) this.helperRest = this.newHelperRest()
675
+ let res
676
+ try { // get issuer and jwks_uri from well-knonw uri
677
+ res = await this.helperRest.doRequest('undefined', 'GET', wellKnownUri)
678
+ } catch (err: any) {
679
+ logger.error(`${gwName}[${pluginName}] JWKS wellKnownUri=${wellKnownUri} error: ${err.message}`)
680
+ return []
681
+ }
682
+ if (!res?.body) return []
683
+ const issuer = res.body.issuer
684
+ const jwks_uri = res.body.jwks_uri
685
+ if (!issuer || !jwks_uri) {
686
+ logger.error(`${gwName}[${pluginName}] JWKS wellKnownUri=${wellKnownUri} error: found issuer=${issuer} and jwks_uri=${jwks_uri} - both should be found`)
687
+ return []
688
+ }
689
+ // JWKS
690
+ try { // get jwks that correspods with kid from jwks_uri
691
+ res = await this.helperRest.doRequest('undefined', 'GET', jwks_uri)
692
+ } catch (err: any) {
693
+ logger.error(`${gwName}[${pluginName}] JWKS jwks_uri=${jwks_uri} error: ${err.message}`)
694
+ return []
695
+ }
696
+ if (!res || !Array.isArray(res?.body?.keys)) return []
697
+ const keys = res.body.keys.filter((k: any) => k.kid === kid)
698
+ if (keys.length !== 1) return []
699
+ try {
700
+ return [jwkToPem(keys[0]), issuer, keys[0].alg]
701
+ } catch (err: any) {
702
+ return []
703
+ }
704
+ }
705
+
706
+ const jwtVerify = async (baseEntity: string, method: string, el: Record<string, any>, authToken: string, kid?: string): Promise<boolean> => { // used by bearerJwt
707
+ try {
708
+ jwt.verify(authToken, (el.secret) ? el.secret : el.publicKeyContent, el.options)
709
+ if (Array.isArray(el?.baseEntities) && el.baseEntities.length > 0) {
710
+ if (!el.baseEntities.includes(baseEntity)) return false
711
+ }
712
+ if (el.readOnly === true && method !== 'GET') return false
713
+ return true // authorization OK
714
+ } catch (err: any) {
715
+ if (!el.wellKnownUri) throw new Error(`JWT error: ${err.message}`)
716
+ // using wellKnownUri - JWKS and external public certificate - try once again we might have an updated certificate (key)
717
+ if (!kid) throw new Error(`JWKS error: missing kid`)
718
+ const [pemKey, issuer, alg] = await getJwksPemKey(kid, el.wellKnownUri)
719
+ if (!pemKey) throw new Error('JWKS error: no external public certificate found')
720
+ el.publicKeyContent = pemKey
721
+ if (!el.options) el.options = {}
722
+ if (issuer) el.options.issuer = issuer
723
+ if (alg) el.options.algorithms = [alg]
724
+ try {
725
+ jwt.verify(authToken, el.publicKeyContent, el.options)
726
+ if (Array.isArray(el?.baseEntities) && el.baseEntities.length > 0) {
727
+ if (!el.baseEntities.includes(baseEntity)) return false
686
728
  }
687
- })
688
- })
729
+ if (el.readOnly === true && method !== 'GET') return false
730
+ return true // authorization OK
731
+ } catch (err: any) {
732
+ throw new Error(`JWKS error: ${err.message}`)
733
+ }
734
+ }
689
735
  }
690
736
 
691
737
  const bearerJwt = async (baseEntity: string, method: string, authType: string, authToken: string): Promise<boolean> => {
692
738
  if (authType !== 'Bearer' || !found.BearerJwt) return false // no standard jwt bearer token
693
739
  const jtoken: any = jwt.decode(authToken, { complete: true })
694
- if (jtoken == null) return false
740
+ if (!jtoken) return false
695
741
  if (jtoken?.payload['iss'] && jtoken?.payload['iss'].indexOf('https://sts.windows.net') === 0) return false // azure - handled by bearerJwtAzure
696
- const promises: any = []
697
742
  const arr = this.config.scimgateway.auth.bearerJwt
698
743
  for (let i = 0; i < arr.length; i++) {
699
- promises.push(jwtVerify(baseEntity, method, arr[i], authToken))
700
- }
701
- const arrResolve = await Promise.all(promises).catch((err) => { throw (err) })
702
- for (const i in arrResolve) {
703
- if (arrResolve[i]) return true
744
+ if (await jwtVerify(baseEntity, method, arr[i], authToken, jtoken?.header?.kid) === true) return true
704
745
  }
705
746
  throw new Error('JWT authentication failed')
706
747
  }
@@ -786,7 +827,7 @@ export class ScimGateway {
786
827
  ])
787
828
  .catch((err) => { throw (err) })
788
829
  for (const i in arrResolve) {
789
- if (arrResolve[i]) return true // auth OK - continue with routes
830
+ if (arrResolve[i] === true) return true // auth OK - continue with routes
790
831
  }
791
832
  // all false - invalid auth method or missing pluging config
792
833
  let err: Error
@@ -827,7 +868,6 @@ export class ScimGateway {
827
868
  }
828
869
  return false
829
870
  }
830
- return false
831
871
  }
832
872
 
833
873
  const ipAllowList = (ipAddr: string): boolean => {
@@ -3554,7 +3594,7 @@ Content-Transfer-Encoding: quoted-printable
3554
3594
  if (lastKey === 'password' && key.startsWith('scimgateway.auth.basic')) foundBasic = true
3555
3595
  else if (lastKey === 'token' && key.startsWith('scimgateway.auth.bearerToken')) foundBearerToken = true
3556
3596
  else if (lastKey === 'tenantIdGUID' && key.startsWith('scimgateway.auth.bearerJwtAzure')) foundBearerJwtAzure = true
3557
- else if (lastKey === 'secret' && key.startsWith('scimgateway.auth.bearerJwt')) foundBearerJwt = true
3597
+ else if ((lastKey === 'publicKey' || lastKey === 'secret' || lastKey === 'wellKnownUri') && key.startsWith('scimgateway.auth.bearerJwt')) foundBearerJwt = true
3558
3598
  else if (lastKey === 'clientSecret' && key.startsWith('scimgateway.auth.bearerOAuth')) foundBearerOAuth = true
3559
3599
 
3560
3600
  // certificate full path
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "scimgateway",
3
- "version": "5.4.3",
3
+ "version": "5.4.4",
4
4
  "type": "module",
5
5
  "description": "Using SCIM protocol as a gateway for user provisioning to other endpoints",
6
6
  "author": "Jarle Elshaug <jarle.elshaug@gmail.com> (https://elshaug.xyz)",
@@ -48,6 +48,7 @@
48
48
  "hyco-https": "^1.4.5",
49
49
  "is-in-subnet": "^4.0.1",
50
50
  "jsonwebtoken": "^9.0.2",
51
+ "jwk-to-pem": "^2.0.7",
51
52
  "ldapjs": "^3.0.7",
52
53
  "lokijs": "^1.5.12",
53
54
  "mongodb": "^6.16.0",
@@ -65,6 +66,7 @@
65
66
  "@types/bun": "latest",
66
67
  "@types/dot-object": "^2.1.6",
67
68
  "@types/jsonwebtoken": "^9.0.9",
69
+ "@types/jwk-to-pem": "^2.0.3",
68
70
  "@types/node": "latest",
69
71
  "@types/nodemailer": "^6.4.17",
70
72
  "@types/passport": "^1.0.17",