scimgateway 5.4.2 → 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 +88 -60
- package/bun.lock +22 -0
- package/config/plugin-api.json +1 -0
- package/config/plugin-entra-id.json +1 -0
- package/config/plugin-ldap.json +1 -0
- package/config/plugin-loki.json +1 -0
- package/config/plugin-mongodb.json +1 -0
- package/config/plugin-mssql.json +1 -0
- package/config/plugin-saphana.json +1 -0
- package/config/plugin-scim.json +1 -0
- package/config/plugin-soap.json +1 -0
- package/lib/helper-rest.ts +1 -1
- package/lib/scimgateway.ts +132 -119
- package/package.json +3 -1
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
|
|
@@ -394,7 +395,7 @@ Definitions in `endpoint` object are customized according to our plugin code. Pl
|
|
|
394
395
|
|
|
395
396
|
- **log.loglevel.console** - off, debug, info, warn or error. Default off. Output to stdout and errors to stderr
|
|
396
397
|
|
|
397
|
-
- **log.loglevel.push** -
|
|
398
|
+
- **log.loglevel.push** - debug, info, warn or error. Default info. Push to stream used by remote real-time log subscription
|
|
398
399
|
|
|
399
400
|
- **log.logDirectory** - custom defined log directory e.g. `/var/log/scimgateway` that will override default `<scimgateway path>/logs`. If not exist it will be created.
|
|
400
401
|
|
|
@@ -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 **
|
|
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
|
|
|
@@ -746,13 +747,18 @@ Please see code editor method HelperRest doRequest() IntelliSense for type and o
|
|
|
746
747
|
### Configuration notes - Remote real-time log subscription
|
|
747
748
|
Using remote real-time log subscription we may implement custom logic like monitoring and centralized logging
|
|
748
749
|
|
|
749
|
-
-
|
|
750
|
-
- curl -
|
|
751
|
-
|
|
752
|
-
|
|
750
|
+
- browser and url: https://host/logger
|
|
751
|
+
- curl with -u or -H "Authorization: Bearer secret"
|
|
752
|
+
```
|
|
753
|
+
curl -Ns http://localhost:8880/logger -u gwadmin:password | awk '
|
|
754
|
+
/^data: / {sub(/^data: /,""); printf "%s", $0; last=1; next}
|
|
755
|
+
/^$/ {if (last) print ""; last=0}
|
|
756
|
+
'
|
|
757
|
+
```
|
|
753
758
|
- custom client API (see example below)
|
|
754
759
|
- not supported by Azure Relay
|
|
755
760
|
|
|
761
|
+
|
|
756
762
|
We may configure read-only user/secret for log collection purpose
|
|
757
763
|
|
|
758
764
|
"auth": {
|
|
@@ -792,67 +798,57 @@ Example using debug loglevel:
|
|
|
792
798
|
Example code implementing remote real-time log subscription and custom message handling
|
|
793
799
|
|
|
794
800
|
```
|
|
795
|
-
//
|
|
796
|
-
//
|
|
797
|
-
|
|
798
|
-
|
|
799
|
-
|
|
800
|
-
const
|
|
801
|
-
|
|
802
|
-
|
|
803
|
-
|
|
804
|
-
}
|
|
805
|
-
|
|
806
|
-
|
|
807
|
-
|
|
801
|
+
//
|
|
802
|
+
// usage: bun <scriptname.ts>
|
|
803
|
+
// update url and the auth according to environment used
|
|
804
|
+
//
|
|
805
|
+
const username = "gwadmin"
|
|
806
|
+
const password = "password"
|
|
807
|
+
const url = "http://localhost:8880/logger"
|
|
808
|
+
|
|
809
|
+
const headers = new Headers({
|
|
810
|
+
Authorization: "Basic " + btoa(`${username}:${password}`),
|
|
811
|
+
Accept: "text/event-stream"
|
|
812
|
+
})
|
|
813
|
+
|
|
814
|
+
// message handling and custom logic
|
|
815
|
+
// we could also do JSON.parse(message) and granular filtering on log "level"
|
|
808
816
|
const messageHandler = async (message: string) => {
|
|
809
817
|
console.log(message)
|
|
810
818
|
}
|
|
811
819
|
|
|
812
|
-
|
|
813
|
-
|
|
814
|
-
|
|
815
|
-
headers
|
|
816
|
-
|
|
817
|
-
|
|
818
|
-
|
|
819
|
-
|
|
820
|
-
|
|
821
|
-
// message is received
|
|
822
|
-
ws.addEventListener("message", event => {
|
|
823
|
-
messageHandler(event.data)
|
|
824
|
-
})
|
|
825
|
-
|
|
826
|
-
// socket opened
|
|
827
|
-
ws.addEventListener("open", event => {
|
|
820
|
+
async function startup() {
|
|
821
|
+
while (true) {
|
|
822
|
+
try {
|
|
823
|
+
const resp = await fetch(url, { headers });
|
|
824
|
+
if (!resp.ok || !resp.body) {
|
|
825
|
+
console.error(`❌ Response error: ${resp.status} ${resp.statusText}`)
|
|
826
|
+
await Bun.sleep(10_000)
|
|
827
|
+
continue
|
|
828
|
+
}
|
|
828
829
|
console.log('✅ Now awaiting log events...\n')
|
|
829
|
-
})
|
|
830
|
-
|
|
831
|
-
// socket closed
|
|
832
|
-
ws.addEventListener("close", event => {
|
|
833
|
-
let addInfo = ''
|
|
834
|
-
if (event.code === 1002) addInfo = ' => most likely authentication failure?'
|
|
835
|
-
console.warn(`⚠️ Connection closed (${event.code}): ${event.reason || 'no reason'}${addInfo}`)
|
|
836
|
-
retry()
|
|
837
|
-
})
|
|
838
|
-
|
|
839
|
-
// error handler
|
|
840
|
-
ws.addEventListener("error", event => {
|
|
841
|
-
// console.error('❌ WebSocket error:', event.message)
|
|
842
|
-
})
|
|
843
|
-
|
|
844
|
-
} catch (err: any) {
|
|
845
|
-
console.error('❌ Unexpected error:', err)
|
|
846
|
-
}
|
|
847
|
-
}
|
|
848
830
|
|
|
849
|
-
const
|
|
850
|
-
|
|
851
|
-
|
|
852
|
-
|
|
831
|
+
const reader = resp.body.pipeThrough(new TextDecoderStream()).getReader()
|
|
832
|
+
|
|
833
|
+
while (true) {
|
|
834
|
+
const { value, done } = await reader.read()
|
|
835
|
+
if (done) break
|
|
836
|
+
if (!value.startsWith('data: ')) continue
|
|
837
|
+
const i = value.indexOf("\n\n")
|
|
838
|
+
if (i < 1) continue
|
|
839
|
+
const msg = value.slice(6, i)
|
|
840
|
+
messageHandler(msg)
|
|
841
|
+
}
|
|
842
|
+
console.error("⚠️ Connection closed");
|
|
843
|
+
await Bun.sleep(10_000)
|
|
844
|
+
} catch (err: any) {
|
|
845
|
+
console.error("❌ Connection error:", err?.message || err)
|
|
846
|
+
await Bun.sleep(10_000)
|
|
847
|
+
}
|
|
848
|
+
}
|
|
853
849
|
}
|
|
854
850
|
|
|
855
|
-
|
|
851
|
+
startup()
|
|
856
852
|
```
|
|
857
853
|
|
|
858
854
|
### Configuration notes - Azure Relay
|
|
@@ -1471,7 +1467,39 @@ In code editor (e.g., Visual Studio Code), method details and documentation are
|
|
|
1471
1467
|
MIT © [Jarle Elshaug](https://www.elshaug.xyz)
|
|
1472
1468
|
|
|
1473
1469
|
|
|
1474
|
-
## 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
|
+
}
|
|
1493
|
+
|
|
1494
|
+
### v5.4.3
|
|
1495
|
+
|
|
1496
|
+
[Fixed]
|
|
1497
|
+
|
|
1498
|
+
- helper-rest, fixed an issue introduced in v5.3.8 that caused problems using OAuth
|
|
1499
|
+
|
|
1500
|
+
[Improved]
|
|
1501
|
+
|
|
1502
|
+
- Remote real-time logger
|
|
1475
1503
|
|
|
1476
1504
|
### v5.4.2
|
|
1477
1505
|
|
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=="],
|
package/config/plugin-api.json
CHANGED
package/config/plugin-ldap.json
CHANGED
package/config/plugin-loki.json
CHANGED
package/config/plugin-mssql.json
CHANGED
package/config/plugin-scim.json
CHANGED
package/config/plugin-soap.json
CHANGED
package/lib/helper-rest.ts
CHANGED
|
@@ -578,7 +578,7 @@ export class HelperRest {
|
|
|
578
578
|
options.headers['Authorization'] = 'Basic ' + Buffer.from(`${o.auth?.options?.username}:${o.auth?.options?.password}`).toString('base64')
|
|
579
579
|
delete o.auth
|
|
580
580
|
}
|
|
581
|
-
options = utils.extendObj(
|
|
581
|
+
options = utils.extendObj(options, o)
|
|
582
582
|
}
|
|
583
583
|
|
|
584
584
|
const cli: any = {}
|
package/lib/scimgateway.ts
CHANGED
|
@@ -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
|
|
674
|
-
|
|
675
|
-
|
|
676
|
-
|
|
677
|
-
|
|
678
|
-
|
|
679
|
-
|
|
680
|
-
|
|
681
|
-
|
|
682
|
-
|
|
683
|
-
|
|
684
|
-
|
|
685
|
-
|
|
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
|
|
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
|
-
|
|
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 => {
|
|
@@ -878,23 +918,24 @@ export class ScimGateway {
|
|
|
878
918
|
const getHandlerLoggerSSE = async (ctx: Context) => {
|
|
879
919
|
const levelInt = logger.levelToInt(this.config?.scimgateway?.log?.loglevel?.push || 'info')
|
|
880
920
|
const encoder = new TextEncoder()
|
|
921
|
+
logger.info(`${gwName}[${pluginName}] remote logger connected from ip address ${ctx.ip}`, { baseEntity: ctx?.routeObj?.baseEntity })
|
|
881
922
|
|
|
882
923
|
return new Response(
|
|
883
924
|
new ReadableStream({
|
|
884
925
|
start(controller) {
|
|
885
|
-
controller.enqueue(encoder.encode(
|
|
926
|
+
controller.enqueue(encoder.encode(`: keep-alive\n\n`))
|
|
886
927
|
|
|
887
928
|
const sub = async (msgObj: Record<string, any>) => {
|
|
888
929
|
if (logger.levelToInt(msgObj.level) < levelInt) return
|
|
889
930
|
if (ctx?.routeObj?.baseEntity !== 'undefined') { // if using baseEntity e.g. <host>/company1/logger, only include corresponding baseEntity logentries
|
|
890
931
|
if (ctx?.routeObj?.baseEntity !== msgObj.baseEntity) return
|
|
891
932
|
}
|
|
892
|
-
controller.enqueue(encoder.encode(
|
|
933
|
+
controller.enqueue(encoder.encode(`data: ${JSON.stringify(msgObj)}\n\n`))
|
|
893
934
|
}
|
|
894
935
|
logger.subscribe(sub)
|
|
895
936
|
|
|
896
937
|
const keepAliveInterval = setInterval(() => {
|
|
897
|
-
controller.enqueue(encoder.encode(
|
|
938
|
+
controller.enqueue(encoder.encode(`: keep-alive\n\n`))
|
|
898
939
|
}, 10000)
|
|
899
940
|
|
|
900
941
|
const cleanup = () => {
|
|
@@ -2705,15 +2746,39 @@ export class ScimGateway {
|
|
|
2705
2746
|
const isPublisherEnabled = this.config.scimgateway.stream.publisher.enabled
|
|
2706
2747
|
const isChainingEnabled = this.config.scimgateway.chainingBaseUrl
|
|
2707
2748
|
|
|
2708
|
-
const
|
|
2749
|
+
const sseInit = `
|
|
2709
2750
|
<!DOCTYPE html>
|
|
2710
2751
|
<html>
|
|
2711
2752
|
<head>
|
|
2712
2753
|
<style>
|
|
2754
|
+
html, body {
|
|
2755
|
+
height: 100%;
|
|
2756
|
+
margin: 0;
|
|
2757
|
+
padding: 0;
|
|
2758
|
+
}
|
|
2759
|
+
body {
|
|
2760
|
+
display: flex;
|
|
2761
|
+
flex-direction: column;
|
|
2762
|
+
height: 100vh;
|
|
2763
|
+
margin-left: 8px;
|
|
2764
|
+
}
|
|
2713
2765
|
.header-flex {
|
|
2714
2766
|
display: flex;
|
|
2715
2767
|
align-items: center;
|
|
2716
2768
|
gap: 16px;
|
|
2769
|
+
flex-shrink: 0;
|
|
2770
|
+
margin-top: 2px;
|
|
2771
|
+
margin-bottom: 2px;
|
|
2772
|
+
}
|
|
2773
|
+
#log {
|
|
2774
|
+
flex: 1 1 auto;
|
|
2775
|
+
width: 100%;
|
|
2776
|
+
overflow: auto;
|
|
2777
|
+
white-space: pre;
|
|
2778
|
+
margin: 0;
|
|
2779
|
+
min-height: 0;
|
|
2780
|
+
height: auto;
|
|
2781
|
+
box-sizing: border-box;
|
|
2717
2782
|
}
|
|
2718
2783
|
#stopBtn {
|
|
2719
2784
|
padding: 4px 18px;
|
|
@@ -2728,42 +2793,41 @@ export class ScimGateway {
|
|
|
2728
2793
|
</head>
|
|
2729
2794
|
<body>
|
|
2730
2795
|
<div class="header-flex">
|
|
2731
|
-
<h3
|
|
2796
|
+
<h3>SCIM Gateway remote logger</h3>
|
|
2732
2797
|
<button id="stopBtn" type="button">Stop</button>
|
|
2733
2798
|
</div>
|
|
2734
2799
|
<pre id="log"></pre>
|
|
2735
2800
|
<script>
|
|
2736
2801
|
const stopBtn = document.getElementById('stopBtn')
|
|
2737
2802
|
const logElem = document.getElementById('log')
|
|
2738
|
-
let
|
|
2739
|
-
|
|
2740
|
-
|
|
2741
|
-
|
|
2742
|
-
|
|
2743
|
-
|
|
2744
|
-
|
|
2745
|
-
|
|
2746
|
-
|
|
2747
|
-
|
|
2748
|
-
|
|
2749
|
-
|
|
2750
|
-
|
|
2751
|
-
|
|
2752
|
-
}
|
|
2753
|
-
return p1 + '<span style="color:' + color + ';font-weight:bold">' + p2 + '</span>'
|
|
2803
|
+
let es = new EventSource(location.pathname)
|
|
2804
|
+
|
|
2805
|
+
es.onmessage = function(event) {
|
|
2806
|
+
if (!event.data.trim()) return
|
|
2807
|
+
const htmlLine = event.data.replace(
|
|
2808
|
+
/(level":"\s*)(debug|info|warn|error)/i,
|
|
2809
|
+
function(match, p1, p2) {
|
|
2810
|
+
let color = ''
|
|
2811
|
+
switch (p2.toLowerCase()) {
|
|
2812
|
+
case 'debug': color = '#888'; break
|
|
2813
|
+
case 'info': color = 'blue'; break
|
|
2814
|
+
case 'warn': color = 'orange'; break
|
|
2815
|
+
case 'error': color = 'red'; break
|
|
2816
|
+
default: color = 'black'
|
|
2754
2817
|
}
|
|
2755
|
-
|
|
2756
|
-
|
|
2757
|
-
|
|
2818
|
+
return p1 + '<span style="color:' + color + ';font-weight:bold">' + p2 + '</span>'
|
|
2819
|
+
}
|
|
2820
|
+
)
|
|
2821
|
+
logElem.innerHTML += htmlLine + '<br>'
|
|
2822
|
+
logElem.scrollTop = logElem.scrollHeight
|
|
2758
2823
|
}
|
|
2824
|
+
|
|
2759
2825
|
stopBtn.onclick = function() {
|
|
2760
|
-
if (
|
|
2761
|
-
|
|
2762
|
-
|
|
2826
|
+
if (es) {
|
|
2827
|
+
es.close()
|
|
2828
|
+
es = null
|
|
2763
2829
|
stopBtn.textContent = 'Start'
|
|
2764
|
-
stopBtn.onclick = function() {
|
|
2765
|
-
location.reload()
|
|
2766
|
-
}
|
|
2830
|
+
stopBtn.onclick = function() { location.reload() }
|
|
2767
2831
|
}
|
|
2768
2832
|
}
|
|
2769
2833
|
</script>
|
|
@@ -2807,29 +2871,18 @@ export class ScimGateway {
|
|
|
2807
2871
|
await getHandlerServiceProviderConfig(ctx)
|
|
2808
2872
|
return await onAfterHandle(ctx)
|
|
2809
2873
|
case 'GET logger': // no onAfterHandle
|
|
2810
|
-
if (req.headers.
|
|
2811
|
-
|
|
2812
|
-
|
|
2813
|
-
|
|
2814
|
-
|
|
2815
|
-
|
|
2816
|
-
|
|
2817
|
-
|
|
2818
|
-
|
|
2819
|
-
}
|
|
2820
|
-
}
|
|
2821
|
-
}
|
|
2822
|
-
if (req.headers.has('sec-fetch-dest') && typeof Bun !== 'undefined') { // client is browser and not supporting WebSocket on Node.js
|
|
2823
|
-
const url = new URL(ctx.origin)
|
|
2824
|
-
const protocol = (url.protocol === 'https:' ? 'wss:' : 'ws:')
|
|
2825
|
-
const js = wssInit.replace('{{protocol}}', protocol)
|
|
2826
|
-
return new Response(js, { // browser step 1 => force WebSocket by sending javascript
|
|
2827
|
-
status: 200,
|
|
2828
|
-
headers: {
|
|
2829
|
-
'Content-Type': 'text/html; charset=utf-8',
|
|
2830
|
-
},
|
|
2831
|
-
})
|
|
2832
|
-
} else return await getHandlerLoggerSSE(ctx) // using SSE for none WebSocket/wss e.g. curl -Ns http://localhost:8880/logger -u gwadmin:password | sed 's/\xE2\x80\x8B//g'
|
|
2874
|
+
if (req.headers.has('sec-fetch-dest')) { // client is browser
|
|
2875
|
+
if (ctx.request.headers.get('accept')?.includes('text/event-stream')) {
|
|
2876
|
+
return await getHandlerLoggerSSE(ctx)
|
|
2877
|
+
} else {
|
|
2878
|
+
return new Response(sseInit, {
|
|
2879
|
+
status: 200,
|
|
2880
|
+
headers: {
|
|
2881
|
+
'Content-Type': 'text/html; charset=utf-8',
|
|
2882
|
+
},
|
|
2883
|
+
})
|
|
2884
|
+
}
|
|
2885
|
+
} else return await getHandlerLoggerSSE(ctx)
|
|
2833
2886
|
case 'PATCH users':
|
|
2834
2887
|
case 'PATCH groups':
|
|
2835
2888
|
await patchHandler(ctx)
|
|
@@ -2884,49 +2937,9 @@ export class ScimGateway {
|
|
|
2884
2937
|
const reqWithRaw = req as Request & { raw: IncomingMessage }
|
|
2885
2938
|
return await route(reqWithRaw, srv.requestIP(req)?.address || '')
|
|
2886
2939
|
},
|
|
2887
|
-
websocket: {
|
|
2888
|
-
open: (ws) => {
|
|
2889
|
-
const data = ws.data as { headers: Headers, url: string, baseEntity: string, ip: string, nonce: string } || {}
|
|
2890
|
-
let isAuthorized = false // client is already authenticated by initial http/https upgrade to websocket, anyhow passing data to be validated
|
|
2891
|
-
if (data?.nonce && this.Nonce.isItemValid(data.nonce)) {
|
|
2892
|
-
if (data.headers.has('authorization')) {
|
|
2893
|
-
if (data.url.endsWith('/logger')) isAuthorized = true
|
|
2894
|
-
}
|
|
2895
|
-
}
|
|
2896
|
-
if (!isAuthorized) {
|
|
2897
|
-
logger.error(`${gwName}[${pluginName}] remote logger ip address ${data.ip} - WebSocket connection error: invalid nonce`, { baseEntity: data.baseEntity })
|
|
2898
|
-
ws.close(3000, 'Unauthorized')
|
|
2899
|
-
return
|
|
2900
|
-
}
|
|
2901
|
-
|
|
2902
|
-
const levelInt = logger.levelToInt(this.config?.scimgateway?.log?.loglevel?.push || 'info')
|
|
2903
|
-
const sub = async (msgObj: Record<string, any>) => {
|
|
2904
|
-
if (logger.levelToInt(msgObj.level) < levelInt) return
|
|
2905
|
-
if (data.baseEntity !== 'undefined') { // if using baseEntity e.g. <host>/company1/logger, only include corresponding baseEntity logentries
|
|
2906
|
-
if (data.baseEntity !== msgObj.baseEntity) return
|
|
2907
|
-
}
|
|
2908
|
-
ws.send(`${JSON.stringify(msgObj)}`)
|
|
2909
|
-
}
|
|
2910
|
-
logger.subscribe(sub)
|
|
2911
|
-
;(ws as any)._sub = sub
|
|
2912
|
-
;(ws as any)._baseEntity = data.baseEntity
|
|
2913
|
-
;(ws as any)._ip = data.ip
|
|
2914
|
-
},
|
|
2915
|
-
close: (ws) => {
|
|
2916
|
-
const sub = (ws as any)._sub
|
|
2917
|
-
const baseEntity = (ws as any)._baseEntity
|
|
2918
|
-
const ip = (ws as any)._ip
|
|
2919
|
-
if (sub) {
|
|
2920
|
-
logger.unsubscribe(sub)
|
|
2921
|
-
}
|
|
2922
|
-
logger.info(`${gwName}[${pluginName}] remote logger disconnected from ip address ${ip}`, { baseEntity })
|
|
2923
|
-
},
|
|
2924
|
-
message: () => {},
|
|
2925
|
-
},
|
|
2926
2940
|
})
|
|
2927
2941
|
} else {
|
|
2928
2942
|
// using nodejs server either through Bun compability or Node.js
|
|
2929
|
-
|
|
2930
2943
|
// get body from req
|
|
2931
2944
|
async function getRequestBody(req: any): Promise<Buffer> {
|
|
2932
2945
|
return new Promise((resolve, reject) => {
|
|
@@ -3581,7 +3594,7 @@ Content-Transfer-Encoding: quoted-printable
|
|
|
3581
3594
|
if (lastKey === 'password' && key.startsWith('scimgateway.auth.basic')) foundBasic = true
|
|
3582
3595
|
else if (lastKey === 'token' && key.startsWith('scimgateway.auth.bearerToken')) foundBearerToken = true
|
|
3583
3596
|
else if (lastKey === 'tenantIdGUID' && key.startsWith('scimgateway.auth.bearerJwtAzure')) foundBearerJwtAzure = true
|
|
3584
|
-
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
|
|
3585
3598
|
else if (lastKey === 'clientSecret' && key.startsWith('scimgateway.auth.bearerOAuth')) foundBearerOAuth = true
|
|
3586
3599
|
|
|
3587
3600
|
// certificate full path
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "scimgateway",
|
|
3
|
-
"version": "5.4.
|
|
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",
|