scimgateway 5.4.0 → 5.4.2
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 +53 -38
- package/lib/logger.ts +11 -10
- package/lib/scimgateway.ts +107 -67
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -799,51 +799,51 @@ const auth = 'Basic ' + btoa('gwadmin' + ':' + 'password') // const auth = 'Bear
|
|
|
799
799
|
|
|
800
800
|
const tls: any = {}
|
|
801
801
|
if (url.startsWith('wss:')) {
|
|
802
|
-
|
|
803
|
-
|
|
802
|
+
tls.ca = [Bun.file('/path/to/self-signed-cert.pem')], // only needed for self-signed certs
|
|
803
|
+
tls.rejectUnauthorized = false
|
|
804
804
|
}
|
|
805
805
|
|
|
806
806
|
// messageHandler implements message handling and custom logic
|
|
807
807
|
// could also use JSON.parse(message) and granular filtering on log "level"
|
|
808
808
|
const messageHandler = async (message: string) => {
|
|
809
|
-
|
|
809
|
+
console.log(message)
|
|
810
810
|
}
|
|
811
811
|
|
|
812
812
|
const startWebSocket = async () => {
|
|
813
|
-
|
|
814
|
-
|
|
815
|
-
|
|
816
|
-
|
|
817
|
-
|
|
818
|
-
|
|
819
|
-
|
|
820
|
-
|
|
821
|
-
|
|
822
|
-
|
|
823
|
-
|
|
824
|
-
|
|
825
|
-
|
|
826
|
-
|
|
827
|
-
|
|
828
|
-
|
|
829
|
-
|
|
830
|
-
|
|
831
|
-
|
|
832
|
-
|
|
833
|
-
|
|
834
|
-
|
|
835
|
-
|
|
836
|
-
|
|
837
|
-
|
|
838
|
-
|
|
839
|
-
|
|
840
|
-
|
|
841
|
-
|
|
842
|
-
|
|
843
|
-
|
|
844
|
-
|
|
845
|
-
|
|
846
|
-
|
|
813
|
+
try {
|
|
814
|
+
const ws = new WebSocket(url, {
|
|
815
|
+
headers: {
|
|
816
|
+
Authorization: auth,
|
|
817
|
+
},
|
|
818
|
+
tls,
|
|
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 => {
|
|
828
|
+
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
847
|
}
|
|
848
848
|
|
|
849
849
|
const retry = async () => {
|
|
@@ -1473,11 +1473,26 @@ MIT © [Jarle Elshaug](https://www.elshaug.xyz)
|
|
|
1473
1473
|
|
|
1474
1474
|
## Change log
|
|
1475
1475
|
|
|
1476
|
+
### v5.4.2
|
|
1477
|
+
|
|
1478
|
+
[Improved]
|
|
1479
|
+
|
|
1480
|
+
- baseEntity included as json-key in logs
|
|
1481
|
+
- Remote real-time logger now supports baseEntity. `http(s)://host/logger` gives all log entries for plugin. `http(s)://host/<baseEntity>/logger` gives only log entries for the baseEntity used.
|
|
1482
|
+
|
|
1483
|
+
Note, using `baseEntity` is optional. This is a parameter used for multi tenant or multi endpoint solutions. We could create several endpoint configurations having unique baseEntity. Also note that we can configure auth linked to baseEntity including readOnly.
|
|
1484
|
+
|
|
1485
|
+
### v5.4.1
|
|
1486
|
+
|
|
1487
|
+
[Improved]
|
|
1488
|
+
|
|
1489
|
+
- Remote real-time logger, stop/start button added when using browser
|
|
1490
|
+
|
|
1476
1491
|
### v5.4.0
|
|
1477
1492
|
|
|
1478
1493
|
[Improved]
|
|
1479
1494
|
|
|
1480
|
-
-
|
|
1495
|
+
- Some underlying enhancements have been made to the remote real-time logger. When using a browser, log level colors are now shown. Note: the remote logger is not supported via Azure Relay
|
|
1481
1496
|
|
|
1482
1497
|
### v5.3.8
|
|
1483
1498
|
|
package/lib/logger.ts
CHANGED
|
@@ -294,32 +294,33 @@ export class Logger {
|
|
|
294
294
|
* @param level log level
|
|
295
295
|
* @param message the message that will be logged
|
|
296
296
|
*/
|
|
297
|
-
private async log(level: 'debug' | 'info' | 'warn' | 'error', message: string) {
|
|
297
|
+
private async log(level: 'debug' | 'info' | 'warn' | 'error', message: string, obj?: Record<string, any>) {
|
|
298
298
|
const time = new Date().toISOString()
|
|
299
299
|
message = this.maskSecret(message)
|
|
300
300
|
const msgObj: Record<string, any> = {
|
|
301
301
|
time,
|
|
302
|
-
category: this.category,
|
|
303
302
|
level,
|
|
303
|
+
category: this.category,
|
|
304
|
+
...(obj || {}),
|
|
304
305
|
message,
|
|
305
306
|
}
|
|
306
307
|
this.logChannel.publish(msgObj)
|
|
307
308
|
}
|
|
308
309
|
|
|
309
|
-
public debug(message: string) {
|
|
310
|
-
this.log('debug', message)
|
|
310
|
+
public debug(message: string, obj?: Record<string, any>) {
|
|
311
|
+
this.log('debug', message, obj)
|
|
311
312
|
}
|
|
312
313
|
|
|
313
|
-
public info(message: string) {
|
|
314
|
-
this.log('info', message)
|
|
314
|
+
public info(message: string, obj?: Record<string, any>) {
|
|
315
|
+
this.log('info', message, obj)
|
|
315
316
|
}
|
|
316
317
|
|
|
317
|
-
public warn(message: string) {
|
|
318
|
-
this.log('warn', message)
|
|
318
|
+
public warn(message: string, obj?: Record<string, any>) {
|
|
319
|
+
this.log('warn', message, obj)
|
|
319
320
|
}
|
|
320
321
|
|
|
321
|
-
public error(message: string) {
|
|
322
|
-
this.log('error', message)
|
|
322
|
+
public error(message: string, obj?: Record<string, any>) {
|
|
323
|
+
this.log('error', message, obj)
|
|
323
324
|
}
|
|
324
325
|
|
|
325
326
|
/**
|
package/lib/scimgateway.ts
CHANGED
|
@@ -586,11 +586,11 @@ export class ScimGateway {
|
|
|
586
586
|
|
|
587
587
|
if (ctx.response.status && (ctx.response.status < 200 || ctx.response.status > 299)) {
|
|
588
588
|
if (ctx.response.status === 412 || ctx.response.status === 304) {
|
|
589
|
-
logger.info(`${gwName}[${pluginName}][${ctx?.routeObj?.baseEntity}] ${ellapsed} ${ctx.ip} ${userName} ${ctx.response.status} ${ctx.request.method} ${ctx.request.url} Inbound=${JSON.stringify(ctx.request.body)} Outbound=${outbound}
|
|
589
|
+
logger.info(`${gwName}[${pluginName}][${ctx?.routeObj?.baseEntity}] ${ellapsed} ${ctx.ip} ${userName} ${ctx.response.status} ${ctx.request.method} ${ctx.request.url} Inbound=${JSON.stringify(ctx.request.body)} Outbound=${outbound}`, { baseEntity: ctx?.routeObj?.baseEntity })
|
|
590
590
|
} else if (ctx.response.status === 404) {
|
|
591
|
-
logger.warn(`${gwName}[${pluginName}][${ctx?.routeObj?.baseEntity}] ${ellapsed} ${ctx.ip} ${userName} ${ctx.response.status} ${ctx.request.method} ${ctx.request.url} Inbound=${JSON.stringify(ctx.request.body)} Outbound=${outbound}
|
|
592
|
-
} else logger.error(`${gwName}[${pluginName}][${ctx?.routeObj?.baseEntity}] ${ellapsed} ${ctx.ip} ${userName} ${ctx.response.status} ${ctx.request.method} ${ctx.request.url} Inbound=${JSON.stringify(ctx.request.body)} Outbound=${outbound}
|
|
593
|
-
} else logger.info(`${gwName}[${pluginName}][${ctx?.routeObj?.baseEntity}] ${ellapsed} ${ctx.ip} ${ctx.response.status} ${userName} ${ctx.request.method} ${ctx.request.url} Inbound=${JSON.stringify(ctx.request.body)} Outbound=${outbound}
|
|
591
|
+
logger.warn(`${gwName}[${pluginName}][${ctx?.routeObj?.baseEntity}] ${ellapsed} ${ctx.ip} ${userName} ${ctx.response.status} ${ctx.request.method} ${ctx.request.url} Inbound=${JSON.stringify(ctx.request.body)} Outbound=${outbound}`, { baseEntity: ctx?.routeObj?.baseEntity })
|
|
592
|
+
} else logger.error(`${gwName}[${pluginName}][${ctx?.routeObj?.baseEntity}] ${ellapsed} ${ctx.ip} ${userName} ${ctx.response.status} ${ctx.request.method} ${ctx.request.url} Inbound=${JSON.stringify(ctx.request.body)} Outbound=${outbound}`, { baseEntity: ctx?.routeObj?.baseEntity })
|
|
593
|
+
} else logger.info(`${gwName}[${pluginName}][${ctx?.routeObj?.baseEntity}] ${ellapsed} ${ctx.ip} ${ctx.response.status} ${userName} ${ctx.request.method} ${ctx.request.url} Inbound=${JSON.stringify(ctx.request.body)} Outbound=${outbound}`, { baseEntity: ctx?.routeObj?.baseEntity })
|
|
594
594
|
}
|
|
595
595
|
|
|
596
596
|
// start auth methods - used by auth
|
|
@@ -793,12 +793,12 @@ export class ScimGateway {
|
|
|
793
793
|
if (authType.length < 1) err = new Error(`${ctx.request.url} request is missing authentication information`)
|
|
794
794
|
else {
|
|
795
795
|
err = new Error(`${ctx.request.url} request having unsupported authentication or plugin configuration is missing`)
|
|
796
|
-
logger.debug(`${gwName}[${pluginName}][${ctx?.routeObj?.baseEntity}] request authToken = ${authToken}
|
|
797
|
-
logger.debug(`${gwName}[${pluginName}][${ctx?.routeObj?.baseEntity}] request jwt.decode(authToken) = ${JSON.stringify(jwt.decode(authToken))}
|
|
796
|
+
logger.debug(`${gwName}[${pluginName}][${ctx?.routeObj?.baseEntity}] request authToken = ${authToken}`, { baseEntity: ctx?.routeObj?.baseEntity })
|
|
797
|
+
logger.debug(`${gwName}[${pluginName}][${ctx?.routeObj?.baseEntity}] request jwt.decode(authToken) = ${JSON.stringify(jwt.decode(authToken))}`, { baseEntity: ctx?.routeObj?.baseEntity })
|
|
798
798
|
}
|
|
799
799
|
if (authType === 'Bearer') ctx.response.headers.set('WWW-Authenticate', 'Bearer realm=""')
|
|
800
800
|
else if (found.Basic) ctx.response.headers.set('WWW-Authenticate', 'Basic realm=""')
|
|
801
|
-
if (ctx.request.url !== '/favicon.ico' && !ctx.request.url.startsWith('/apple-touch-icon')) logger.error(`${gwName}[${pluginName}][${ctx?.routeObj?.baseEntity}] ${err.message}
|
|
801
|
+
if (ctx.request.url !== '/favicon.ico' && !ctx.request.url.startsWith('/apple-touch-icon')) logger.error(`${gwName}[${pluginName}][${ctx?.routeObj?.baseEntity}] ${err.message}`, { baseEntity: ctx?.routeObj?.baseEntity })
|
|
802
802
|
return false
|
|
803
803
|
} catch (err: any) {
|
|
804
804
|
if (authType === 'Bearer') {
|
|
@@ -818,9 +818,9 @@ export class ScimGateway {
|
|
|
818
818
|
} else ctx.response.headers.set('WWW-Authenticate', 'Basic realm=""')
|
|
819
819
|
if (pwErrCount < 3) {
|
|
820
820
|
pwErrCount += 1
|
|
821
|
-
logger.error(`${gwName}[${pluginName}][${ctx?.routeObj?.baseEntity}] ${ctx.request.url} ${err.message}
|
|
821
|
+
logger.error(`${gwName}[${pluginName}][${ctx?.routeObj?.baseEntity}] ${ctx.request.url} ${err.message}`, { baseEntity: ctx?.routeObj?.baseEntity })
|
|
822
822
|
} else { // delay brute force attempts
|
|
823
|
-
logger.error(`${gwName}[${pluginName}][${ctx?.routeObj?.baseEntity}] ${ctx.request.url} ${err.message} => delaying response with 2 minutes to prevent brute force
|
|
823
|
+
logger.error(`${gwName}[${pluginName}][${ctx?.routeObj?.baseEntity}] ${ctx.request.url} ${err.message} => delaying response with 2 minutes to prevent brute force`, { baseEntity: ctx?.routeObj?.baseEntity })
|
|
824
824
|
await new Promise((resolve) => {
|
|
825
825
|
setTimeout(() => { resolve(null) }, 1000 * 60 * 2)
|
|
826
826
|
})
|
|
@@ -833,7 +833,6 @@ export class ScimGateway {
|
|
|
833
833
|
const ipAllowList = (ipAddr: string): boolean => {
|
|
834
834
|
if (ipAllowListChecker === undefined) return true
|
|
835
835
|
if (ipAllowListChecker(ipAddr) === true) return true // if proxy, prereq: request includes header X-Forwarded-For
|
|
836
|
-
logger.debug(`${gwName}[${pluginName}] client ip ${ipAddr} not in ipAllowList`)
|
|
837
836
|
return false
|
|
838
837
|
}
|
|
839
838
|
|
|
@@ -887,6 +886,9 @@ export class ScimGateway {
|
|
|
887
886
|
|
|
888
887
|
const sub = async (msgObj: Record<string, any>) => {
|
|
889
888
|
if (logger.levelToInt(msgObj.level) < levelInt) return
|
|
889
|
+
if (ctx?.routeObj?.baseEntity !== 'undefined') { // if using baseEntity e.g. <host>/company1/logger, only include corresponding baseEntity logentries
|
|
890
|
+
if (ctx?.routeObj?.baseEntity !== msgObj.baseEntity) return
|
|
891
|
+
}
|
|
890
892
|
controller.enqueue(encoder.encode(`${JSON.stringify(msgObj)}\n`))
|
|
891
893
|
}
|
|
892
894
|
logger.subscribe(sub)
|
|
@@ -899,7 +901,7 @@ export class ScimGateway {
|
|
|
899
901
|
clearInterval(keepAliveInterval)
|
|
900
902
|
logger.unsubscribe(sub)
|
|
901
903
|
controller.close()
|
|
902
|
-
logger.info(`${gwName}[${pluginName}] remote logger disconnected from ip address ${ctx.ip}
|
|
904
|
+
logger.info(`${gwName}[${pluginName}] remote logger disconnected from ip address ${ctx.ip}`, { baseEntity: ctx?.routeObj?.baseEntity })
|
|
903
905
|
}
|
|
904
906
|
|
|
905
907
|
ctx.request.signal.onabort = cleanup // Bun
|
|
@@ -921,9 +923,9 @@ export class ScimGateway {
|
|
|
921
923
|
|
|
922
924
|
// oauth token request, POST /oauth/token
|
|
923
925
|
const postHandlerOauthToken = async (ctx: Context) => {
|
|
924
|
-
logger.debug(`${gwName}[${pluginName}][${ctx?.routeObj?.baseEntity}] [oauth] token request
|
|
926
|
+
logger.debug(`${gwName}[${pluginName}][${ctx?.routeObj?.baseEntity}] [oauth] token request`, { baseEntity: ctx?.routeObj?.baseEntity })
|
|
925
927
|
if (!found.BearerOAuth) {
|
|
926
|
-
logger.error(`${gwName}[${pluginName}][${ctx?.routeObj?.baseEntity}] [oauth] token request, but plugin is missing auth.bearerOAuth configuration
|
|
928
|
+
logger.error(`${gwName}[${pluginName}][${ctx?.routeObj?.baseEntity}] [oauth] token request, but plugin is missing auth.bearerOAuth configuration`, { baseEntity: ctx?.routeObj?.baseEntity })
|
|
927
929
|
ctx.response.status = 500
|
|
928
930
|
return
|
|
929
931
|
}
|
|
@@ -931,7 +933,7 @@ export class ScimGateway {
|
|
|
931
933
|
try {
|
|
932
934
|
if (!jsonBody) throw new Error('missing body')
|
|
933
935
|
if (typeof jsonBody !== 'object') { // might have application/x-www-form-urlencoded or multipart/form-data body, but incorrect Content-Type header
|
|
934
|
-
logger.debug(`${gwName}[${pluginName}][${ctx?.routeObj?.baseEntity}] [oauth] continue request validation even though incorrect body vs header Content-Type: ${ctx.request.headers.get('content-type')}
|
|
936
|
+
logger.debug(`${gwName}[${pluginName}][${ctx?.routeObj?.baseEntity}] [oauth] continue request validation even though incorrect body vs header Content-Type: ${ctx.request.headers.get('content-type')}`, { baseEntity: ctx?.routeObj?.baseEntity })
|
|
935
937
|
let body = utils.formUrlEncodedToJSON(jsonBody)
|
|
936
938
|
if (Object.keys(body).length < 1) {
|
|
937
939
|
body = utils.formDataMultipartToJSON(jsonBody)
|
|
@@ -942,7 +944,7 @@ export class ScimGateway {
|
|
|
942
944
|
}
|
|
943
945
|
jsonBody = utils.copyObj(jsonBody) // no changes to original
|
|
944
946
|
} catch (err: any) {
|
|
945
|
-
logger.error(`${gwName}[${pluginName}][${ctx?.routeObj?.baseEntity}] [oauth] token request error: ${err.message}
|
|
947
|
+
logger.error(`${gwName}[${pluginName}][${ctx?.routeObj?.baseEntity}] [oauth] token request error: ${err.message}`, { baseEntity: ctx?.routeObj?.baseEntity })
|
|
946
948
|
ctx.response.status = 401
|
|
947
949
|
return
|
|
948
950
|
}
|
|
@@ -1001,7 +1003,7 @@ export class ScimGateway {
|
|
|
1001
1003
|
}
|
|
1002
1004
|
|
|
1003
1005
|
if (err) {
|
|
1004
|
-
logger.error(`${gwName}[${pluginName}][${ctx?.routeObj?.baseEntity}] [oauth] token request client_id: ${jsonBody ? jsonBody.client_id : ''} error: ${errDescr}
|
|
1006
|
+
logger.error(`${gwName}[${pluginName}][${ctx?.routeObj?.baseEntity}] [oauth] token request client_id: ${jsonBody ? jsonBody.client_id : ''} error: ${errDescr}`, { baseEntity: ctx?.routeObj?.baseEntity })
|
|
1005
1007
|
ctx.response.status = 401
|
|
1006
1008
|
const errMsg = {
|
|
1007
1009
|
error: err,
|
|
@@ -1063,12 +1065,12 @@ export class ScimGateway {
|
|
|
1063
1065
|
value: id,
|
|
1064
1066
|
}
|
|
1065
1067
|
|
|
1066
|
-
logger.debug(`${gwName}[${pluginName}][${ctx?.routeObj?.baseEntity}] [Get ${handle.description}] ${getObj.attribute}=${getObj.value}
|
|
1068
|
+
logger.debug(`${gwName}[${pluginName}][${ctx?.routeObj?.baseEntity}] [Get ${handle.description}] ${getObj.attribute}=${getObj.value}`, { baseEntity: ctx?.routeObj?.baseEntity })
|
|
1067
1069
|
|
|
1068
1070
|
try {
|
|
1069
1071
|
const ob = utils.copyObj(getObj)
|
|
1070
1072
|
const attributes = ctx.query.attributes ? ctx.query.attributes.split(',').map((item: string) => item.trim()) : []
|
|
1071
|
-
logger.debug(`${gwName}[${pluginName}][${ctx?.routeObj?.baseEntity}] calling ${handle.getMethod} and awaiting result
|
|
1073
|
+
logger.debug(`${gwName}[${pluginName}][${ctx?.routeObj?.baseEntity}] calling ${handle.getMethod} and awaiting result`, { baseEntity: ctx?.routeObj?.baseEntity })
|
|
1072
1074
|
let res = await (this as any)[handle.getMethod](baseEntity, ob, [], ctx.passThrough)
|
|
1073
1075
|
|
|
1074
1076
|
let scimdata: { [key: string]: any } = {
|
|
@@ -1273,7 +1275,7 @@ export class ScimGateway {
|
|
|
1273
1275
|
|
|
1274
1276
|
let info = ''
|
|
1275
1277
|
if (getObj.operator === 'eq' && ['id', 'userName', 'externalId', 'displayName', 'members.value'].includes(getObj.attribute)) info = ` ${getObj.attribute}=${getObj.value}`
|
|
1276
|
-
logger.debug(`${gwName}[${pluginName}][${ctx?.routeObj?.baseEntity}] [Get ${handle.description}s]${info}
|
|
1278
|
+
logger.debug(`${gwName}[${pluginName}][${ctx?.routeObj?.baseEntity}] [Get ${handle.description}s]${info}`, { baseEntity: ctx?.routeObj?.baseEntity })
|
|
1277
1279
|
try {
|
|
1278
1280
|
getObj.startIndex = ctx.query.startIndex ? parseInt(ctx.query.startIndex) : undefined
|
|
1279
1281
|
getObj.count = ctx.query.count ? parseInt(ctx.query.count) : undefined
|
|
@@ -1309,7 +1311,7 @@ export class ScimGateway {
|
|
|
1309
1311
|
}
|
|
1310
1312
|
const chunk = 5
|
|
1311
1313
|
const chunkRes: Record<string, any>[] = []
|
|
1312
|
-
logger.debug(`${gwName}[${pluginName}][${ctx?.routeObj?.baseEntity}] calling ${handle.getMethod} with chunks and awaiting result
|
|
1314
|
+
logger.debug(`${gwName}[${pluginName}][${ctx?.routeObj?.baseEntity}] calling ${handle.getMethod} with chunks and awaiting result`, { baseEntity: ctx?.routeObj?.baseEntity })
|
|
1313
1315
|
do {
|
|
1314
1316
|
const arrChunk = getObjArr.splice(0, chunk)
|
|
1315
1317
|
const results = await Promise.allSettled(arrChunk.map(o => getObj(o))) as { status: 'fulfilled' | 'rejected', reason: any, value: any }[] // processing max chunk async
|
|
@@ -1328,7 +1330,7 @@ export class ScimGateway {
|
|
|
1328
1330
|
}
|
|
1329
1331
|
|
|
1330
1332
|
if (!res) { // standard
|
|
1331
|
-
logger.debug(`${gwName}[${pluginName}][${ctx?.routeObj?.baseEntity}] calling ${handle.getMethod} and awaiting result
|
|
1333
|
+
logger.debug(`${gwName}[${pluginName}][${ctx?.routeObj?.baseEntity}] calling ${handle.getMethod} and awaiting result`, { baseEntity: ctx?.routeObj?.baseEntity })
|
|
1332
1334
|
res = await (this as any)[handle.getMethod](baseEntity, obj, [], ctx.passThrough)
|
|
1333
1335
|
}
|
|
1334
1336
|
// check for user attribute groups and include if needed
|
|
@@ -1392,7 +1394,7 @@ export class ScimGateway {
|
|
|
1392
1394
|
const postHandler = async (ctx: Context) => {
|
|
1393
1395
|
const handle = handler[ctx.routeObj.handle]
|
|
1394
1396
|
const baseEntity = ctx.routeObj.baseEntity
|
|
1395
|
-
logger.debug(`${gwName}[${pluginName}][${ctx?.routeObj?.baseEntity}] [Create ${handle.description}]
|
|
1397
|
+
logger.debug(`${gwName}[${pluginName}][${ctx?.routeObj?.baseEntity}] [Create ${handle.description}]`, { baseEntity: ctx?.routeObj?.baseEntity })
|
|
1396
1398
|
let jsonBody = ctx.request.body
|
|
1397
1399
|
try {
|
|
1398
1400
|
if (!jsonBody) throw new Error('missing body')
|
|
@@ -1422,9 +1424,9 @@ export class ScimGateway {
|
|
|
1422
1424
|
return
|
|
1423
1425
|
}
|
|
1424
1426
|
|
|
1425
|
-
logger.debug(`${gwName}[${pluginName}][${ctx?.routeObj?.baseEntity}] POST ${ctx.origin + ctx.path} body=${JSON.stringify(jsonBody)}
|
|
1427
|
+
logger.debug(`${gwName}[${pluginName}][${ctx?.routeObj?.baseEntity}] POST ${ctx.origin + ctx.path} body=${JSON.stringify(jsonBody)}`, { baseEntity: ctx?.routeObj?.baseEntity })
|
|
1426
1428
|
const [scimdata, err] = utilsScim.convertedScim(jsonBody, this.multiValueTypes)
|
|
1427
|
-
logger.debug(`${gwName}[${pluginName}][${ctx?.routeObj?.baseEntity}] convertedBody=${JSON.stringify(scimdata)}
|
|
1429
|
+
logger.debug(`${gwName}[${pluginName}][${ctx?.routeObj?.baseEntity}] convertedBody=${JSON.stringify(scimdata)}`, { baseEntity: ctx?.routeObj?.baseEntity })
|
|
1428
1430
|
if (err) {
|
|
1429
1431
|
ctx.response.status = 500
|
|
1430
1432
|
const [e, customErrorCode] = utilsScim.jsonErr(this.config.scimgateway.scim.version, pluginName, ctx.response.status, err)
|
|
@@ -1444,7 +1446,7 @@ export class ScimGateway {
|
|
|
1444
1446
|
delete scimdata.groups
|
|
1445
1447
|
}
|
|
1446
1448
|
}
|
|
1447
|
-
logger.debug(`${gwName}[${pluginName}][${ctx?.routeObj?.baseEntity}] calling ${handle.createMethod} and awaiting result
|
|
1449
|
+
logger.debug(`${gwName}[${pluginName}][${ctx?.routeObj?.baseEntity}] calling ${handle.createMethod} and awaiting result`, { baseEntity: ctx?.routeObj?.baseEntity })
|
|
1448
1450
|
const res = await (this as any)[handle.createMethod](baseEntity, scimdata, ctx.passThrough)
|
|
1449
1451
|
for (const key in res) { // merge any result e.g: {'id': 'xxxx'}
|
|
1450
1452
|
jsonBody[key] = res[key]
|
|
@@ -1531,7 +1533,7 @@ export class ScimGateway {
|
|
|
1531
1533
|
ctx.response.body = JSON.stringify(e)
|
|
1532
1534
|
return
|
|
1533
1535
|
}
|
|
1534
|
-
logger.debug(`${gwName}[${pluginName}][${ctx?.routeObj?.baseEntity}] [Delete ${handle.description}] id=${id}
|
|
1536
|
+
logger.debug(`${gwName}[${pluginName}][${ctx?.routeObj?.baseEntity}] [Delete ${handle.description}] id=${id}`, { baseEntity: ctx?.routeObj?.baseEntity })
|
|
1535
1537
|
|
|
1536
1538
|
try {
|
|
1537
1539
|
if (handle.deleteMethod === 'deleteUser') {
|
|
@@ -1547,7 +1549,7 @@ export class ScimGateway {
|
|
|
1547
1549
|
})) // result not handled - ignore any failures
|
|
1548
1550
|
}
|
|
1549
1551
|
}
|
|
1550
|
-
logger.debug(`${gwName}[${pluginName}][${ctx?.routeObj?.baseEntity}] calling ${handle.deleteMethod} and awaiting result
|
|
1552
|
+
logger.debug(`${gwName}[${pluginName}][${ctx?.routeObj?.baseEntity}] calling ${handle.deleteMethod} and awaiting result`, { baseEntity: ctx?.routeObj?.baseEntity })
|
|
1551
1553
|
await (this as any)[handle.deleteMethod](baseEntity, id, ctx.passThrough)
|
|
1552
1554
|
ctx.response.status = 204
|
|
1553
1555
|
} catch (err: any) {
|
|
@@ -1589,7 +1591,7 @@ export class ScimGateway {
|
|
|
1589
1591
|
return
|
|
1590
1592
|
}
|
|
1591
1593
|
|
|
1592
|
-
logger.debug(`${gwName}[${pluginName}][${ctx?.routeObj?.baseEntity}] [Modify ${handle.description}] id=${id}
|
|
1594
|
+
logger.debug(`${gwName}[${pluginName}][${ctx?.routeObj?.baseEntity}] [Modify ${handle.description}] id=${id}`, { baseEntity: ctx?.routeObj?.baseEntity })
|
|
1593
1595
|
|
|
1594
1596
|
const eTagIfMatch = ctx.request.headers.get('if-match')?.split(',').map((item: string) => item.trim()).filter(Boolean)
|
|
1595
1597
|
const eTagIfNoneMatch = ctx.request.headers.get('if-none-match')?.split(',').map((item: string) => item.trim()).filter(Boolean)
|
|
@@ -1597,7 +1599,7 @@ export class ScimGateway {
|
|
|
1597
1599
|
let eTag = ''
|
|
1598
1600
|
if (handle.getMethod === handler.users.getMethod || handle.getMethod === handler.groups.getMethod) { // getUsers or getGroups implemented
|
|
1599
1601
|
const ob = { attribute: 'id', operator: 'eq', value: id }
|
|
1600
|
-
logger.debug(`${gwName}[${pluginName}][${ctx?.routeObj?.baseEntity}] calling ${handle.getMethod} and awaiting result
|
|
1602
|
+
logger.debug(`${gwName}[${pluginName}][${ctx?.routeObj?.baseEntity}] calling ${handle.getMethod} and awaiting result`, { baseEntity: ctx?.routeObj?.baseEntity })
|
|
1601
1603
|
const res = await (this as any)[handle.getMethod](baseEntity, ob, [], ctx.passThrough)
|
|
1602
1604
|
if (res) {
|
|
1603
1605
|
if (res.Resources && Array.isArray(res.Resources)) {
|
|
@@ -1629,7 +1631,7 @@ export class ScimGateway {
|
|
|
1629
1631
|
let scimdata: any, err: any
|
|
1630
1632
|
if (jsonBody.Operations) [scimdata, err] = utilsScim.convertedScim20(jsonBody, this.multiValueTypes) // v2.0
|
|
1631
1633
|
else [scimdata, err] = utilsScim.convertedScim(jsonBody, this.multiValueTypes) // v1.1
|
|
1632
|
-
logger.debug(`${gwName}[${pluginName}][${ctx?.routeObj?.baseEntity}] convertedBody=${JSON.stringify(scimdata)}
|
|
1634
|
+
logger.debug(`${gwName}[${pluginName}][${ctx?.routeObj?.baseEntity}] convertedBody=${JSON.stringify(scimdata)}`, { baseEntity: ctx?.routeObj?.baseEntity })
|
|
1633
1635
|
if (err) {
|
|
1634
1636
|
ctx.response.status = 500
|
|
1635
1637
|
const [e, customErrorCode] = utilsScim.jsonErr(this.config.scimgateway.scim.version, pluginName, ctx.response.status, err)
|
|
@@ -1655,7 +1657,7 @@ export class ScimGateway {
|
|
|
1655
1657
|
if (Array.isArray(scimdata.members) && scimdata.members.length === 0 && handle.modifyMethod === 'modifyGroup') {
|
|
1656
1658
|
res = await replaceUsrGrp(ctx.routeObj.handle, baseEntity, id, scimdata, this.config.scimgateway.scim.usePutSoftSync, ctx.passThrough, undefined)
|
|
1657
1659
|
} else {
|
|
1658
|
-
logger.debug(`${gwName}[${pluginName}][${ctx?.routeObj?.baseEntity}] calling ${handle.modifyMethod} and awaiting result
|
|
1660
|
+
logger.debug(`${gwName}[${pluginName}][${ctx?.routeObj?.baseEntity}] calling ${handle.modifyMethod} and awaiting result`, { baseEntity: ctx?.routeObj?.baseEntity })
|
|
1659
1661
|
res = await (this as any)[handle.modifyMethod](baseEntity, id, scimdata, ctx.passThrough)
|
|
1660
1662
|
}
|
|
1661
1663
|
|
|
@@ -1680,7 +1682,7 @@ export class ScimGateway {
|
|
|
1680
1682
|
return
|
|
1681
1683
|
}
|
|
1682
1684
|
const ob = { attribute: 'id', operator: 'eq', value: id }
|
|
1683
|
-
logger.debug(`${gwName}[${pluginName}][${ctx?.routeObj?.baseEntity}] calling ${handle.getMethod} and awaiting result
|
|
1685
|
+
logger.debug(`${gwName}[${pluginName}][${ctx?.routeObj?.baseEntity}] calling ${handle.getMethod} and awaiting result`, { baseEntity: ctx?.routeObj?.baseEntity })
|
|
1684
1686
|
res = await (this as any)[handle.getMethod](baseEntity, ob, [], ctx.passThrough)
|
|
1685
1687
|
}
|
|
1686
1688
|
|
|
@@ -1732,9 +1734,9 @@ export class ScimGateway {
|
|
|
1732
1734
|
id = decodeURIComponent(id)
|
|
1733
1735
|
|
|
1734
1736
|
// get current object
|
|
1735
|
-
logger.debug(`${gwName}[${pluginName}][${baseEntity}] calling ${handle.getMethod} and awaiting result
|
|
1737
|
+
logger.debug(`${gwName}[${pluginName}][${baseEntity}] calling ${handle.getMethod} and awaiting result`, { baseEntity })
|
|
1736
1738
|
const res = await (this as any)[handle.getMethod](baseEntity, { attribute: 'id', operator: 'eq', value: id }, [], ctxPassThrough)
|
|
1737
|
-
logger.debug(`${gwName}[${pluginName}][${baseEntity}] "${handle.getMethod}" result: ${res ? JSON.stringify(res) : ''}
|
|
1739
|
+
logger.debug(`${gwName}[${pluginName}][${baseEntity}] "${handle.getMethod}" result: ${res ? JSON.stringify(res) : ''}`, { baseEntity })
|
|
1738
1740
|
let currentObj
|
|
1739
1741
|
if (res && res.Resources && Array.isArray(res.Resources)) {
|
|
1740
1742
|
if (res.Resources.length === 1) currentObj = res.Resources[0]
|
|
@@ -1792,7 +1794,7 @@ export class ScimGateway {
|
|
|
1792
1794
|
|
|
1793
1795
|
// update object
|
|
1794
1796
|
if (Object.keys(scimdata).length > 0) {
|
|
1795
|
-
logger.debug(`${gwName}[${pluginName}][${baseEntity}] calling ${handle.modifyMethod} and awaiting result
|
|
1797
|
+
logger.debug(`${gwName}[${pluginName}][${baseEntity}] calling ${handle.modifyMethod} and awaiting result`, { baseEntity })
|
|
1796
1798
|
await (this as any)[handle.modifyMethod](baseEntity, id, scimdata, ctxPassThrough)
|
|
1797
1799
|
}
|
|
1798
1800
|
|
|
@@ -1808,7 +1810,7 @@ export class ScimGateway {
|
|
|
1808
1810
|
let res: any
|
|
1809
1811
|
try {
|
|
1810
1812
|
res = await (this as any)[handler.groups.getMethod](baseEntity, { attribute: 'members.value', operator: 'eq', value: decodeURIComponent(id) }, ['id', 'displayName'], ctxPassThrough)
|
|
1811
|
-
logger.debug(`${gwName}[${pluginName}][${baseEntity}] "${handler.groups.getMethod}" result: ${res ? JSON.stringify(res) : ''}
|
|
1813
|
+
logger.debug(`${gwName}[${pluginName}][${baseEntity}] "${handler.groups.getMethod}" result: ${res ? JSON.stringify(res) : ''}`, { baseEntity })
|
|
1812
1814
|
} catch (err) { void 0 } // method may be implemented, but throwing error like groups not supported/implemented
|
|
1813
1815
|
currentGroups = []
|
|
1814
1816
|
if (res && res.Resources && Array.isArray(res.Resources) && res.Resources.length > 0) {
|
|
@@ -1889,7 +1891,7 @@ export class ScimGateway {
|
|
|
1889
1891
|
const id = ctx.routeObj.id ? decodeURIComponent(ctx.routeObj.id) : ctx.routeObj.id
|
|
1890
1892
|
const obj = ctx.request.body
|
|
1891
1893
|
|
|
1892
|
-
logger.debug(`${gwName}[${pluginName}][${ctx?.routeObj?.baseEntity}] [PUT ${handle[0].toUpperCase() + handle.slice(1)}] id=${id} body=${JSON.stringify(obj)}
|
|
1894
|
+
logger.debug(`${gwName}[${pluginName}][${ctx?.routeObj?.baseEntity}] [PUT ${handle[0].toUpperCase() + handle.slice(1)}] id=${id} body=${JSON.stringify(obj)}`, { baseEntity: ctx?.routeObj?.baseEntity })
|
|
1893
1895
|
try {
|
|
1894
1896
|
if (!obj) throw new Error('missing body')
|
|
1895
1897
|
if (typeof obj !== 'object') throw new Error('body is not JSON')
|
|
@@ -1946,7 +1948,7 @@ export class ScimGateway {
|
|
|
1946
1948
|
|
|
1947
1949
|
const postBulkHandler = async (ctx: Context) => {
|
|
1948
1950
|
const baseEntity = ctx.routeObj.baseEntity
|
|
1949
|
-
logger.debug(`${gwName}[${pluginName}][${ctx?.routeObj?.baseEntity}] [Bulk Operations]
|
|
1951
|
+
logger.debug(`${gwName}[${pluginName}][${ctx?.routeObj?.baseEntity}] [Bulk Operations]`, { baseEntity: ctx?.routeObj?.baseEntity })
|
|
1950
1952
|
const bulkBody: SCIMBulkRequest = utils.copyObj(ctx.request.body)
|
|
1951
1953
|
try {
|
|
1952
1954
|
if (!bulkBody) throw new Error('missing body')
|
|
@@ -2098,7 +2100,7 @@ export class ScimGateway {
|
|
|
2098
2100
|
const postApiHandler = async (ctx: Context) => {
|
|
2099
2101
|
const baseEntity = ctx.routeObj.baseEntity
|
|
2100
2102
|
const obj = ctx.request.body
|
|
2101
|
-
logger.debug(`${gwName}[${pluginName}][${ctx?.routeObj?.baseEntity}] [POST ${ctx.routeObj.handle}]
|
|
2103
|
+
logger.debug(`${gwName}[${pluginName}][${ctx?.routeObj?.baseEntity}] [POST ${ctx.routeObj.handle}]`, { baseEntity: ctx?.routeObj?.baseEntity })
|
|
2102
2104
|
|
|
2103
2105
|
if (!obj) {
|
|
2104
2106
|
const err = new Error('missing body')
|
|
@@ -2107,7 +2109,7 @@ export class ScimGateway {
|
|
|
2107
2109
|
return
|
|
2108
2110
|
}
|
|
2109
2111
|
try {
|
|
2110
|
-
logger.debug(`${gwName}[${pluginName}][${ctx?.routeObj?.baseEntity}] calling postApi and awaiting result
|
|
2112
|
+
logger.debug(`${gwName}[${pluginName}][${ctx?.routeObj?.baseEntity}] calling postApi and awaiting result`, { baseEntity: ctx?.routeObj?.baseEntity })
|
|
2111
2113
|
let result = await this.postApi(baseEntity, obj, ctx.passThrough)
|
|
2112
2114
|
if (result) {
|
|
2113
2115
|
if (typeof result === 'object') result = { result: result }
|
|
@@ -2147,7 +2149,7 @@ export class ScimGateway {
|
|
|
2147
2149
|
const baseEntity = ctx.routeObj.baseEntity
|
|
2148
2150
|
const id = ctx.routeObj.id
|
|
2149
2151
|
const obj = ctx.request.body
|
|
2150
|
-
logger.debug(`${gwName}[${pluginName}][${ctx?.routeObj?.baseEntity}] [PUT ${ctx.routeObj.handle}] id=${id}
|
|
2152
|
+
logger.debug(`${gwName}[${pluginName}][${ctx?.routeObj?.baseEntity}] [PUT ${ctx.routeObj.handle}] id=${id}`, { baseEntity: ctx?.routeObj?.baseEntity })
|
|
2151
2153
|
|
|
2152
2154
|
try {
|
|
2153
2155
|
if (!obj) throw new Error('missing body')
|
|
@@ -2159,7 +2161,7 @@ export class ScimGateway {
|
|
|
2159
2161
|
}
|
|
2160
2162
|
|
|
2161
2163
|
try {
|
|
2162
|
-
logger.debug(`${gwName}[${pluginName}][${ctx?.routeObj?.baseEntity}] calling putApi and awaiting result
|
|
2164
|
+
logger.debug(`${gwName}[${pluginName}][${ctx?.routeObj?.baseEntity}] calling putApi and awaiting result`, { baseEntity: ctx?.routeObj?.baseEntity })
|
|
2163
2165
|
let result = await this.putApi(baseEntity, id, obj, ctx.passThrough)
|
|
2164
2166
|
if (result) {
|
|
2165
2167
|
if (typeof result === 'object') result = { result }
|
|
@@ -2200,7 +2202,7 @@ export class ScimGateway {
|
|
|
2200
2202
|
const id = ctx.routeObj.id as string
|
|
2201
2203
|
const body = ctx.request.body
|
|
2202
2204
|
|
|
2203
|
-
logger.debug(`${gwName}[${pluginName}][${ctx?.routeObj?.baseEntity}] [PATCH ${handle} ] id=${id}
|
|
2205
|
+
logger.debug(`${gwName}[${pluginName}][${ctx?.routeObj?.baseEntity}] [PATCH ${handle} ] id=${id}`, { baseEntity: ctx?.routeObj?.baseEntity })
|
|
2204
2206
|
|
|
2205
2207
|
if (!body) {
|
|
2206
2208
|
const err = new Error('missing body')
|
|
@@ -2209,7 +2211,7 @@ export class ScimGateway {
|
|
|
2209
2211
|
return
|
|
2210
2212
|
} else {
|
|
2211
2213
|
try {
|
|
2212
|
-
logger.debug(`${gwName}[${pluginName}][${ctx?.routeObj?.baseEntity}] calling patchApi and awaiting result
|
|
2214
|
+
logger.debug(`${gwName}[${pluginName}][${ctx?.routeObj?.baseEntity}] calling patchApi and awaiting result`, { baseEntity: ctx?.routeObj?.baseEntity })
|
|
2213
2215
|
let result = await this.patchApi(baseEntity, id, body, ctx.passThrough)
|
|
2214
2216
|
if (result) {
|
|
2215
2217
|
if (typeof result === 'object') result = { result }
|
|
@@ -2250,11 +2252,11 @@ export class ScimGateway {
|
|
|
2250
2252
|
const id = ctx.routeObj.id as string
|
|
2251
2253
|
const body = ctx.request.body
|
|
2252
2254
|
|
|
2253
|
-
if (id) logger.debug(`${gwName}[${pluginName}][${ctx?.routeObj?.baseEntity}] [GET ${handle}] id=${id}
|
|
2255
|
+
if (id) logger.debug(`${gwName}[${pluginName}][${ctx?.routeObj?.baseEntity}] [GET ${handle}] id=${id}`, { baseEntity: ctx?.routeObj?.baseEntity })
|
|
2254
2256
|
else logger.debug(`${gwName}[${pluginName}][${ctx?.routeObj?.baseEntity}] [GET ${handle}]`)
|
|
2255
2257
|
|
|
2256
2258
|
try {
|
|
2257
|
-
logger.debug(`${gwName}[${pluginName}][${ctx?.routeObj?.baseEntity}] calling getApi and awaiting result
|
|
2259
|
+
logger.debug(`${gwName}[${pluginName}][${ctx?.routeObj?.baseEntity}] calling getApi and awaiting result`, { baseEntity: ctx?.routeObj?.baseEntity })
|
|
2258
2260
|
let result = await this.getApi(baseEntity, id, ctx.query, body, ctx.passThrough)
|
|
2259
2261
|
if (result) {
|
|
2260
2262
|
if (typeof result === 'object') result = { result }
|
|
@@ -2289,10 +2291,10 @@ export class ScimGateway {
|
|
|
2289
2291
|
const deleteApiHandler = async (ctx: Context) => {
|
|
2290
2292
|
const baseEntity = ctx.routeObj.baseEntity
|
|
2291
2293
|
const id = ctx.routeObj.id
|
|
2292
|
-
logger.debug(`${gwName}[${pluginName}][${ctx?.routeObj?.baseEntity}] [DELETE ${ctx.routeObj.handle} ] id=${id}
|
|
2294
|
+
logger.debug(`${gwName}[${pluginName}][${ctx?.routeObj?.baseEntity}] [DELETE ${ctx.routeObj.handle} ] id=${id}`, { baseEntity: ctx?.routeObj?.baseEntity })
|
|
2293
2295
|
try {
|
|
2294
2296
|
if (!id || id.includes('/')) throw new Error('missing id')
|
|
2295
|
-
logger.debug(`${gwName}[${pluginName}][${ctx?.routeObj?.baseEntity}] calling deleteApi and awaiting result
|
|
2297
|
+
logger.debug(`${gwName}[${pluginName}][${ctx?.routeObj?.baseEntity}] calling deleteApi and awaiting result`, { baseEntity: ctx?.routeObj?.baseEntity })
|
|
2296
2298
|
let result = await this.deleteApi(baseEntity, id, ctx.passThrough)
|
|
2297
2299
|
if (result) {
|
|
2298
2300
|
if (typeof result === 'object') result = { result: result }
|
|
@@ -2334,7 +2336,7 @@ export class ScimGateway {
|
|
|
2334
2336
|
try {
|
|
2335
2337
|
const ob = { attribute: 'members.value', operator: 'eq', value: decodeURIComponent(id) }
|
|
2336
2338
|
const attributes = ['id', 'displayName']
|
|
2337
|
-
logger.debug(`${gwName}[${pluginName}][${baseEntity}] calling ${handler.groups.getMethod} and awaiting result - groups to be included
|
|
2339
|
+
logger.debug(`${gwName}[${pluginName}][${baseEntity}] calling ${handler.groups.getMethod} and awaiting result - groups to be included`, { baseEntity })
|
|
2338
2340
|
res = await (this as any)[handler.groups.getMethod](baseEntity, ob, attributes, ctxPassThrough)
|
|
2339
2341
|
} catch (err) { void 0 }
|
|
2340
2342
|
if (res && res.Resources && Array.isArray(res.Resources) && res.Resources.length > 0) {
|
|
@@ -2550,6 +2552,7 @@ export class ScimGateway {
|
|
|
2550
2552
|
} else if (!ctx.routeObj.handle) {
|
|
2551
2553
|
ctx.response.status = 404 // NOT_FOUND
|
|
2552
2554
|
} else if (!ipAllowList(ctx.ip)) {
|
|
2555
|
+
logger.debug(`${gwName}[${pluginName}] client ip ${ctx.ip} not in ipAllowList`, { baseEntity: ctx?.routeObj?.baseEntity })
|
|
2553
2556
|
ctx.response.status = 401
|
|
2554
2557
|
} else if (!await isAuthorized(ctx)) {
|
|
2555
2558
|
ctx.response.status = 401
|
|
@@ -2565,14 +2568,14 @@ export class ScimGateway {
|
|
|
2565
2568
|
const chainingBaseUrl = this.config.scimgateway.chainingBaseUrl // http(s)://<host>:<port>
|
|
2566
2569
|
if (!chainingBaseUrl) {
|
|
2567
2570
|
ctx.response.status = 500
|
|
2568
|
-
logger.error(`${gwName}[${pluginName}] onChainingHandler error: configuration scimgateway.chainingBaseUrl missing
|
|
2571
|
+
logger.error(`${gwName}[${pluginName}] onChainingHandler error: configuration scimgateway.chainingBaseUrl missing`, { baseEntity: ctx?.routeObj?.baseEntity })
|
|
2569
2572
|
return
|
|
2570
2573
|
}
|
|
2571
2574
|
try {
|
|
2572
2575
|
new URL(chainingBaseUrl)
|
|
2573
2576
|
} catch (err: any) {
|
|
2574
2577
|
ctx.response.status = 500
|
|
2575
|
-
logger.error(`${gwName}[${pluginName}] onChainingHandler error: configuration scimgateway.chainingBaseUrl must use correct syntax 'http(s)://host:port' error: ${err.message}
|
|
2578
|
+
logger.error(`${gwName}[${pluginName}] onChainingHandler error: configuration scimgateway.chainingBaseUrl must use correct syntax 'http(s)://host:port' error: ${err.message}`, { baseEntity: ctx?.routeObj?.baseEntity })
|
|
2576
2579
|
return
|
|
2577
2580
|
}
|
|
2578
2581
|
try {
|
|
@@ -2597,7 +2600,7 @@ export class ScimGateway {
|
|
|
2597
2600
|
ctx.response.body = jBody.body ? JSON.stringify(jBody.body) : err.message
|
|
2598
2601
|
} catch (parseErr) {
|
|
2599
2602
|
ctx.response.status = 500
|
|
2600
|
-
logger.error(`${gwName}[${pluginName}] onChainingHandler error: ${err.message}
|
|
2603
|
+
logger.error(`${gwName}[${pluginName}] onChainingHandler error: ${err.message}`, { baseEntity: ctx?.routeObj?.baseEntity })
|
|
2601
2604
|
}
|
|
2602
2605
|
}
|
|
2603
2606
|
}
|
|
@@ -2605,14 +2608,14 @@ export class ScimGateway {
|
|
|
2605
2608
|
const onPublisherHandler = async (ctx: Context) => {
|
|
2606
2609
|
if (!this.pub) {
|
|
2607
2610
|
ctx.response.status = 500
|
|
2608
|
-
logger.error(`${gwName}[${pluginName}] onPublisherHandler error: publisher not initialized
|
|
2611
|
+
logger.error(`${gwName}[${pluginName}] onPublisherHandler error: publisher not initialized`, { baseEntity: ctx?.routeObj?.baseEntity })
|
|
2609
2612
|
return
|
|
2610
2613
|
}
|
|
2611
2614
|
try {
|
|
2612
2615
|
ctx.response = await this.pub.publish({ ctx })
|
|
2613
2616
|
} catch (err: any) {
|
|
2614
2617
|
ctx.response.status = 500
|
|
2615
|
-
logger.error(`${gwName}[${pluginName}] onPublisherHandler error: ${err.message}
|
|
2618
|
+
logger.error(`${gwName}[${pluginName}] onPublisherHandler error: ${err.message}`, { baseEntity: ctx?.routeObj?.baseEntity })
|
|
2616
2619
|
return
|
|
2617
2620
|
}
|
|
2618
2621
|
}
|
|
@@ -2705,20 +2708,41 @@ export class ScimGateway {
|
|
|
2705
2708
|
const wssInit = `
|
|
2706
2709
|
<!DOCTYPE html>
|
|
2707
2710
|
<html>
|
|
2711
|
+
<head>
|
|
2712
|
+
<style>
|
|
2713
|
+
.header-flex {
|
|
2714
|
+
display: flex;
|
|
2715
|
+
align-items: center;
|
|
2716
|
+
gap: 16px;
|
|
2717
|
+
}
|
|
2718
|
+
#stopBtn {
|
|
2719
|
+
padding: 4px 18px;
|
|
2720
|
+
font-size: 12px;
|
|
2721
|
+
background: #eee;
|
|
2722
|
+
border: 1px solid #888;
|
|
2723
|
+
border-radius: 4px;
|
|
2724
|
+
color: #222;
|
|
2725
|
+
cursor: pointer;
|
|
2726
|
+
}
|
|
2727
|
+
</style>
|
|
2728
|
+
</head>
|
|
2708
2729
|
<body>
|
|
2709
|
-
<
|
|
2730
|
+
<div class="header-flex">
|
|
2731
|
+
<h3 style="margin:0;">SCIM Gateway remote logger</h3>
|
|
2732
|
+
<button id="stopBtn" type="button">Stop</button>
|
|
2733
|
+
</div>
|
|
2710
2734
|
<pre id="log"></pre>
|
|
2711
2735
|
<script>
|
|
2736
|
+
const stopBtn = document.getElementById('stopBtn')
|
|
2712
2737
|
const logElem = document.getElementById('log')
|
|
2713
|
-
|
|
2738
|
+
let ws = new WebSocket('{{protocol}}//' + location.host + location.pathname)
|
|
2714
2739
|
ws.onmessage = function(event) {
|
|
2715
2740
|
event.data.split('\\n').forEach(function(line) {
|
|
2716
2741
|
if (!line.trim()) return
|
|
2717
|
-
|
|
2718
|
-
var htmlLine = line.replace(
|
|
2742
|
+
const htmlLine = line.replace(
|
|
2719
2743
|
/(level":"\\s*)(debug|info|warn|error)/i,
|
|
2720
2744
|
function(match, p1, p2) {
|
|
2721
|
-
|
|
2745
|
+
let color = ''
|
|
2722
2746
|
switch (p2.toLowerCase()) {
|
|
2723
2747
|
case 'debug': color = '#888'; break
|
|
2724
2748
|
case 'info': color = 'blue'; break
|
|
@@ -2732,6 +2756,16 @@ export class ScimGateway {
|
|
|
2732
2756
|
logElem.innerHTML += htmlLine + '<br>'
|
|
2733
2757
|
})
|
|
2734
2758
|
}
|
|
2759
|
+
stopBtn.onclick = function() {
|
|
2760
|
+
if (ws) {
|
|
2761
|
+
ws.close()
|
|
2762
|
+
ws = null
|
|
2763
|
+
stopBtn.textContent = 'Start'
|
|
2764
|
+
stopBtn.onclick = function() {
|
|
2765
|
+
location.reload()
|
|
2766
|
+
}
|
|
2767
|
+
}
|
|
2768
|
+
}
|
|
2735
2769
|
</script>
|
|
2736
2770
|
</body>
|
|
2737
2771
|
</html>
|
|
@@ -2774,11 +2808,12 @@ export class ScimGateway {
|
|
|
2774
2808
|
return await onAfterHandle(ctx)
|
|
2775
2809
|
case 'GET logger': // no onAfterHandle
|
|
2776
2810
|
if (req.headers.get('upgrade')?.toLowerCase() === 'websocket') { // browser step 2, and other Bun ws(s) clients
|
|
2777
|
-
logger.info(`${gwName}[${pluginName}] remote logger connected from ip address ${ctx.ip}
|
|
2811
|
+
logger.info(`${gwName}[${pluginName}] remote logger connected from ip address ${ctx.ip}`, { baseEntity: ctx?.routeObj?.baseEntity })
|
|
2778
2812
|
return server.upgrade(req, { // after upgrade, the server will handle the WebSocket connection configured in Bun.serve()
|
|
2779
2813
|
data: { // passed to WebSocket server Bun open handler
|
|
2780
2814
|
headers: req.headers,
|
|
2781
2815
|
url: req.url,
|
|
2816
|
+
baseEntity: ctx?.routeObj?.baseEntity,
|
|
2782
2817
|
ip: ctx.ip,
|
|
2783
2818
|
nonce: this.Nonce.createItem(crypto.randomUUID()),
|
|
2784
2819
|
},
|
|
@@ -2851,7 +2886,7 @@ export class ScimGateway {
|
|
|
2851
2886
|
},
|
|
2852
2887
|
websocket: {
|
|
2853
2888
|
open: (ws) => {
|
|
2854
|
-
const data = ws.data as { headers: Headers, url: string, ip: string, nonce: string } || {}
|
|
2889
|
+
const data = ws.data as { headers: Headers, url: string, baseEntity: string, ip: string, nonce: string } || {}
|
|
2855
2890
|
let isAuthorized = false // client is already authenticated by initial http/https upgrade to websocket, anyhow passing data to be validated
|
|
2856
2891
|
if (data?.nonce && this.Nonce.isItemValid(data.nonce)) {
|
|
2857
2892
|
if (data.headers.has('authorization')) {
|
|
@@ -2859,7 +2894,7 @@ export class ScimGateway {
|
|
|
2859
2894
|
}
|
|
2860
2895
|
}
|
|
2861
2896
|
if (!isAuthorized) {
|
|
2862
|
-
logger.error(`${gwName}[${pluginName}] remote logger ip address ${data.ip} - WebSocket connection error: invalid nonce
|
|
2897
|
+
logger.error(`${gwName}[${pluginName}] remote logger ip address ${data.ip} - WebSocket connection error: invalid nonce`, { baseEntity: data.baseEntity })
|
|
2863
2898
|
ws.close(3000, 'Unauthorized')
|
|
2864
2899
|
return
|
|
2865
2900
|
}
|
|
@@ -2867,19 +2902,24 @@ export class ScimGateway {
|
|
|
2867
2902
|
const levelInt = logger.levelToInt(this.config?.scimgateway?.log?.loglevel?.push || 'info')
|
|
2868
2903
|
const sub = async (msgObj: Record<string, any>) => {
|
|
2869
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
|
+
}
|
|
2870
2908
|
ws.send(`${JSON.stringify(msgObj)}`)
|
|
2871
2909
|
}
|
|
2872
2910
|
logger.subscribe(sub)
|
|
2873
2911
|
;(ws as any)._sub = sub
|
|
2912
|
+
;(ws as any)._baseEntity = data.baseEntity
|
|
2874
2913
|
;(ws as any)._ip = data.ip
|
|
2875
2914
|
},
|
|
2876
2915
|
close: (ws) => {
|
|
2877
2916
|
const sub = (ws as any)._sub
|
|
2917
|
+
const baseEntity = (ws as any)._baseEntity
|
|
2878
2918
|
const ip = (ws as any)._ip
|
|
2879
2919
|
if (sub) {
|
|
2880
2920
|
logger.unsubscribe(sub)
|
|
2881
2921
|
}
|
|
2882
|
-
logger.info(`${gwName}[${pluginName}] remote logger disconnected from ip address ${ip}
|
|
2922
|
+
logger.info(`${gwName}[${pluginName}] remote logger disconnected from ip address ${ip}`, { baseEntity })
|
|
2883
2923
|
},
|
|
2884
2924
|
message: () => {},
|
|
2885
2925
|
},
|
|
@@ -3154,28 +3194,28 @@ export class ScimGateway {
|
|
|
3154
3194
|
* logDebug logs debug message
|
|
3155
3195
|
**/
|
|
3156
3196
|
logDebug(baseEntity: string | undefined, msg: string) {
|
|
3157
|
-
this.logger.debug(`${this.pluginName}[${baseEntity}] ${msg}
|
|
3197
|
+
this.logger.debug(`${this.pluginName}[${baseEntity}] ${msg}`, { baseEntity })
|
|
3158
3198
|
}
|
|
3159
3199
|
|
|
3160
3200
|
/**
|
|
3161
3201
|
* logInfo logs info message
|
|
3162
3202
|
**/
|
|
3163
3203
|
logInfo(baseEntity: string | undefined, msg: string) {
|
|
3164
|
-
this.logger.info(`${this.pluginName}[${baseEntity}] ${msg}
|
|
3204
|
+
this.logger.info(`${this.pluginName}[${baseEntity}] ${msg}`, { baseEntity })
|
|
3165
3205
|
}
|
|
3166
3206
|
|
|
3167
3207
|
/**
|
|
3168
3208
|
* logWarn logs warning message
|
|
3169
3209
|
**/
|
|
3170
3210
|
logWarn(baseEntity: string | undefined, msg: string) {
|
|
3171
|
-
this.logger.warn(`${this.pluginName}[${baseEntity}] ${msg}
|
|
3211
|
+
this.logger.warn(`${this.pluginName}[${baseEntity}] ${msg}`, { baseEntity })
|
|
3172
3212
|
}
|
|
3173
3213
|
|
|
3174
3214
|
/**
|
|
3175
3215
|
* logError logs error message
|
|
3176
3216
|
**/
|
|
3177
3217
|
logError(baseEntity: string | undefined, msg: string) {
|
|
3178
|
-
this.logger.error(`${this.pluginName}[${baseEntity}] ${msg}
|
|
3218
|
+
this.logger.error(`${this.pluginName}[${baseEntity}] ${msg}`, { baseEntity })
|
|
3179
3219
|
}
|
|
3180
3220
|
|
|
3181
3221
|
/**
|
package/package.json
CHANGED