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 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
- tls.ca = [Bun.file('/path/to/self-signed-cert.pem')], // only needed for self-signed certs
803
- tls.rejectUnauthorized = false
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
- console.log(message)
809
+ console.log(message)
810
810
  }
811
811
 
812
812
  const startWebSocket = async () => {
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
- }
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
- - Remote real-time log subscription now prioritize using WebSocket over SSE. Using browser will show loglevel colors. If running Node.js, WebSocket is not supported and SSE will be used. Remote logger is not supported by Azure Relay.
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
  /**
@@ -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
- <h3>SCIM Gateway remote logger</h3>
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
- const ws = new WebSocket('{{protocol}}//' + location.host + '/logger')
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
- // Highlight only the log level
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
- var color = ''
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
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "scimgateway",
3
- "version": "5.4.0",
3
+ "version": "5.4.2",
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)",