scimgateway 5.0.14 → 5.0.15

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/README.md CHANGED
@@ -16,7 +16,7 @@ Validated through IdP's:
16
16
 
17
17
  Latest news:
18
18
 
19
- - Email, onError and sendMail() supports modern REST OAuth for Microsoft Exchange Online (ExO) and Google Workspace Gmail, alongside traditional SMTP Auth for all mail systems. HelperRest supports a wide range of common authentication methods, including basicAuth, bearerAuth, tokenAuth, oauth, oauthSamlAssertion, oauthJwtAssertion and Auth PassTrough
19
+ - Email, onError and sendMail() supports more secure RESTful OAuth for Microsoft Exchange Online (ExO) and Google Workspace Gmail, alongside traditional SMTP Auth for all mail systems. HelperRest supports a wide range of common authentication methods, including basicAuth, bearerAuth, tokenAuth, oauth, oauthSamlBearer, oauthJwtBearer and Auth PassTrough
20
20
  - Major version **v5.0.0** marks a shift to native TypeScript support and prioritizes [Bun](https://bun.sh/) over Node.js. This upgrade requires some modifications to existing plugins.
21
21
  - **BREAKING**: [SCIM Stream](https://elshaug.xyz/docs/scim-stream) is the modern way of user provisioning letting clients subscribe to messages instead of traditional IGA top-down provisioning. SCIM Gateway now offers enhanced functionality with support for message subscription and automated provisioning using SCIM Stream
22
22
  - Authentication PassThrough letting plugin pass authentication directly to endpoint for avoid maintaining secrets at the gateway. E.g., using Entra ID application OAuth
@@ -437,7 +437,7 @@ Definitions in `endpoint` object are customized according to our plugin code. Pl
437
437
  "2603:1056:2000::/48",
438
438
  "2603:1057:2::/48"
439
439
  ]
440
- - **email** - Contains configuration for sending email from plugin or automated error notifications emailOnError. Note, for emailOnError only the first error will be sent until sendInterval have passed. Supporting both SMTP Auth and modern REST OAuth. For OAuth, currently Microsoft Exchange Online (ExO) and Google Workspace Gmail are supported
440
+ - **email** - Sending email from plugin or automated error notifications emailOnError. For emailOnError only the first error will be sent until sendInterval have passed. Supporting both SMTP Auth and modern REST OAuth. For OAuth, currently Microsoft Exchange Online (ExO) and Google Workspace Gmail are supported - see configuration notes
441
441
  - **email.auth** - Authentication configuration
442
442
  - **email.auth.type** - `oauth` or `smtp`
443
443
  - **email.auth.options** - Authentication options - note, different options for type oauth and smtp
@@ -461,45 +461,6 @@ Definitions in `endpoint` object are customized according to our plugin code. Pl
461
461
  - **email.emailOnError.cc** - Optional comma separated list of cc mail addresses
462
462
  - **email.emailOnError.subject** - Optional mail subject, default `SCIM Gateway error message`
463
463
 
464
- **Configuration notes for Microsoft Exchange Online (ExO):**
465
-
466
- - Entra ID application must have application permissions `Mail.Send`
467
- - To prevent the sending of emails from any defined mailboxes, an ExO `ApplicationAccessPolicy` must be defined through PowerShell.
468
-
469
- First create a mail-enabled security-group that only includes those users (mailboxes) the application is allowed to send from
470
- Note, `mail enabled security group` cannot be created from portal, only from admin or admin.exchange console
471
-
472
- ##Connect to Exchange
473
- Install-Module -Name ExchangeOnlineManagement
474
- Connect-ExchangeOnline
475
-
476
- ##Create ApplicationAccessPolicy
477
- New-ApplicationAccessPolicy -AppId <AppClientID> -PolicyScopeGroupId <MailEnabledSecurityGrpId> -AccessRight RestrictAccess -Description "Restrict app to specific mailboxes"
478
-
479
- **Configuration notes for Google Workspace Gmail:**
480
-
481
- - https://console.cloud.google.com
482
- - IAM & Admin > Service Accounts > Create Service Account
483
- - Name=email-sender
484
- - Create and Continue
485
- - Grant this service account access to project - not needed
486
- - Grant users access to this service - not needed
487
- - IAM & Admin > Service Accounts > "email-sender" account > Keys
488
- - Add Key > Create new key > JSON
489
- - download json `serviceAccountKeyFile` file, refere to configuration `email.auth.options.serviceAccountKeyFile`
490
-
491
- - https://admin.google.com
492
- - Security > Access and data control > API controls
493
- - Manage Domain Wide Delegation > Add new
494
- - Client ID = id of service account created
495
- - OAuth scope = https://www.googleapis.com/auth/gmail.send
496
-
497
- - https://admin.google.com
498
- - Billing > Subscriptions - verify Google Workspace license
499
- - Directory > Users > "user"
500
- - Licenses > Edit > enable Google Workspace license
501
- `email.onerror.from` mail address must have Google Workspace Business license
502
-
503
464
  - **stream** - See [SCIM Stream](https://elshaug.xyz/docs/scim-stream) for configuration details
504
465
 
505
466
  - **endpoint** - Contains endpoint specific configuration according to customized **plugin code**.
@@ -561,6 +522,45 @@ Definitions in `endpoint` object are customized according to our plugin code. Pl
561
522
  }
562
523
 
563
524
 
525
+ - Email, using Microsoft Exchange Online (ExO)
526
+
527
+ - Entra ID application must have application permissions `Mail.Send`
528
+ - To prevent the sending of emails from any defined mailboxes, an ExO `ApplicationAccessPolicy` must be defined through PowerShell.
529
+
530
+ First create a mail-enabled security-group that only includes those users (mailboxes) the application is allowed to send from
531
+ Note, `mail enabled security group` cannot be created from portal, only from admin or admin.exchange console
532
+
533
+ ##Connect to Exchange
534
+ Install-Module -Name ExchangeOnlineManagement
535
+ Connect-ExchangeOnline
536
+
537
+ ##Create ApplicationAccessPolicy
538
+ New-ApplicationAccessPolicy -AppId <AppClientID> -PolicyScopeGroupId <MailEnabledSecurityGrpId> -AccessRight RestrictAccess -Description "Restrict app to specific mailboxes"
539
+
540
+ - Email, using Google Workspace Gmail
541
+
542
+ - https://console.cloud.google.com
543
+ - IAM & Admin > Service Accounts > Create Service Account
544
+ - Name=email-sender
545
+ - Create and Continue
546
+ - Grant this service account access to project - not needed
547
+ - Grant users access to this service - not needed
548
+ - IAM & Admin > Service Accounts > "email-sender" account > Keys
549
+ - Add Key > Create new key > JSON
550
+ - download json Service Account Key file, refere to configuration `email.auth.options.serviceAccountKeyFile`
551
+
552
+ - https://admin.google.com
553
+ - Security > Access and data control > API controls
554
+ - Manage Domain Wide Delegation > Add new
555
+ - Client ID = id of service account created
556
+ - OAuth scope = `https://www.googleapis.com/auth/gmail.send`
557
+
558
+ - https://admin.google.com
559
+ - Billing > Subscriptions - verify Google Workspace license
560
+ - Directory > Users > "user"
561
+ - Licenses > Edit > enable Google Workspace license
562
+ `email.emailOnError.from` mail address must have Google Workspace license
563
+
564
564
 
565
565
  ## Manual startup
566
566
 
@@ -1009,17 +1009,17 @@ For JavaScript coding editor you may use [Visual Studio Code](https://code.visua
1009
1009
 
1010
1010
  Preparation:
1011
1011
 
1012
- * Copy "best matching" example plugin e.g. `lib\plugin-mssql.ts` and `config\plugin-mssql.json` and rename both copies to your plugin name prefix e.g. plugin-mine.ts and plugin-mine.json (for SOAP Webservice endpoint we might use plugin-soap as a template)
1012
+ * Copy "best matching" example plugin e.g. `lib\plugin-mssql.ts` and `config\plugin-mssql.json` and rename both copies to your plugin name prefix e.g. plugin-mine.ts and plugin-mine.json
1013
1013
  * Edit plugin-mine.json and define a unique port number for the gateway setting
1014
1014
  * Edit index.ts and include your plugin in the startup e.g. `const plugins = ['mine']');`
1015
- * Start SCIM Gateway and verify. If using CA Provisioning you could setup a SCIM endpoint using the port number you defined
1015
+ * Start SCIM Gateway and verify using using your own SCIM API requests or your IdP/IGA system.
1016
1016
 
1017
1017
  Now we are ready for custom coding by editing plugin-mine.ts
1018
- Coding should be done step by step and each step should be verified and tested before starting the next (they are all highlighted by comments in existing code).
1018
+ Coding should be done step by step and each step should be verified and tested before starting the next
1019
1019
 
1020
- 1. **Turn off group functionality** - getGroups to return empty response
1020
+ 1. **Turn off group functionality** - getGroups to return empty response (gateway automatically use getGroups for some of the methods if groups not included)
1021
1021
  Please see plugin-saphana that do not use groups.
1022
- 2. **getUsers** (test provisioning retrieve accounts)
1022
+ 2. **getUsers** (test provisioning retrieve all accounts and single account)
1023
1023
  4. **createUser** (test provisioning new account)
1024
1024
  5. **deleteUser** (test provisioning delete account)
1025
1025
  6. **modifyUser** (test provisioning modify account)
@@ -1136,6 +1136,12 @@ MIT © [Jarle Elshaug](https://www.elshaug.xyz)
1136
1136
 
1137
1137
  ## Change log
1138
1138
 
1139
+ ### v5.0.15
1140
+
1141
+ [Improved]
1142
+
1143
+ - HelperRest, auth.type=oauthSamlAssertion and auth.type=oauthJwtAssertion have been updated to `oauthSamlBearer` and `oauthJwtBearer` for consistency
1144
+
1139
1145
  ### v5.0.14
1140
1146
 
1141
1147
  [Improved]
@@ -37,7 +37,7 @@ export class HelperRest {
37
37
  }
38
38
  } else if (this.config_entity[baseEntity]?.connection?.auth?.options?.serviceAccountKeyFile) { // Google, setting baseUrls to googleapis
39
39
  const type = this.config_entity[baseEntity]?.connection?.auth?.type
40
- if (type === 'oauthJwtAssertion' || type === 'oauth') { // includes oauth because of email.auth.type
40
+ if (type === 'oauthJwtBearer' || type === 'oauth') { // includes oauth because of email.auth.type
41
41
  this.config_entity[baseEntity].connection.baseUrls = [this.googleUrl]
42
42
  }
43
43
  }
@@ -99,7 +99,7 @@ export class HelperRest {
99
99
  }
100
100
  break
101
101
 
102
- case 'oauthSamlAssertion':
102
+ case 'oauthSamlBearer':
103
103
  tokenUrl = this.config_entity[baseEntity].connection.auth.options.tokenUrl
104
104
  const context = null
105
105
  const cert = fs.readFileSync(this.config_entity[baseEntity].connection.auth.options.certificate.cert).toString()
@@ -123,7 +123,7 @@ export class HelperRest {
123
123
  }
124
124
  break
125
125
 
126
- case 'oauthJwtAssertion':
126
+ case 'oauthJwtBearer':
127
127
  let privateKey = ''
128
128
  let jwtAttr: Record<string, any> = {}
129
129
  const serviceAccountKeyFile = this.config_entity[baseEntity]?.connection?.auth?.options?.serviceAccountKeyFile
@@ -284,7 +284,7 @@ export class HelperRest {
284
284
  }
285
285
 
286
286
  // Support no auth, header based auth (e.g., config {"options":{"headers":{"APIkey":"123"}}}),
287
- // basicAuth, bearerAuth, oauth, tokenAuth, oauthSamlAssertion, oauthJwtAssertion and auth PassTrough using request header authorization
287
+ // basicAuth, bearerAuth, oauth, tokenAuth, oauthSamlBearer, oauthJwtBearer and auth PassTrough using request header authorization
288
288
 
289
289
  let orgConnection: any
290
290
  if (opt?.connection) { // allow overriding/extending configuration connection by caller argument opt.connection
@@ -325,19 +325,19 @@ export class HelperRest {
325
325
  }
326
326
  param.options.headers['Authorization'] = 'Bearer ' + Buffer.from(this.config_entity[baseEntity].connection.auth.options.token).toString('base64')
327
327
  break
328
- case 'oauthSamlAssertion':
328
+ case 'oauthSamlBearer':
329
329
  if (!this.config_entity[baseEntity]?.connection?.auth?.options?.clientId || !this.config_entity[baseEntity]?.connection?.auth?.options?.companyId
330
330
  || !this.config_entity[baseEntity]?.connection?.auth?.options?.certificate?.key) {
331
- const err = new Error(`auth.type 'oauthSamlAssertion' - missing configuration entity.${baseEntity}.connection.auth.options...`)
331
+ const err = new Error(`auth.type 'oauthSamlBearer' - missing configuration entity.${baseEntity}.connection.auth.options...`)
332
332
  throw err
333
333
  }
334
334
  param.accessToken = await this.getAccessToken(baseEntity, ctx)
335
335
  param.options.headers['Authorization'] = `Bearer ${param.accessToken.access_token}`
336
336
  break
337
- case 'oauthJwtAssertion':
337
+ case 'oauthJwtBearer':
338
338
  if (this.config_entity[baseEntity]?.connection?.auth?.options?.serviceAccountKeyFile) { // Google Service Account
339
339
  if (!this.config_entity[baseEntity]?.connection?.auth?.options?.scope || !this.config_entity[baseEntity]?.connection?.auth?.options?.subject) {
340
- const err = new Error(`auth.type 'oauthJwtAssertion' - using auth.options 'serviceAccountKeyFile' also requires mandatory configuration entity.${baseEntity}.connection.auth.options.scope/subject`)
340
+ const err = new Error(`auth.type 'oauthJwtBearer' - using auth.options 'serviceAccountKeyFile' also requires mandatory configuration entity.${baseEntity}.connection.auth.options.scope/subject`)
341
341
  throw err
342
342
  }
343
343
  } else if (!this.config_entity[baseEntity]?.connection?.auth?.options?.tokenUrl
@@ -347,7 +347,7 @@ export class HelperRest {
347
347
  || !this.config_entity[baseEntity]?.connection?.auth?.options?.audience
348
348
  || !this.config_entity[baseEntity]?.connection?.auth?.options?.certificate?.key
349
349
  ) {
350
- const err = new Error(`auth.type 'oauthJwtAssertion' - when not using auth.options 'serviceAccountKeyFile' which is related to Google, following auth.options is mandatory: tokenUrl, scope, subject, issuer, audience, certificate.key`)
350
+ const err = new Error(`auth.type 'oauthJwtBearer' - when not using auth.options 'serviceAccountKeyFile' which is related to Google, following auth.options is mandatory: tokenUrl, scope, subject, issuer, audience, certificate.key`)
351
351
  throw err
352
352
  }
353
353
 
@@ -653,7 +653,7 @@ export class HelperRest {
653
653
  * ```
654
654
  * type defines authentication being used
655
655
  * if type not defined, no authentication used
656
- * valid type is: `basic`, `oauth`, `token`, `bearer` or `oauthSamlAssertion`
656
+ * valid type is: `basic`, `oauth`, `token`, `bearer` or `oauthSamlBearer`
657
657
  *
658
658
  * for each valid type there are different auth.options
659
659
  *
@@ -699,7 +699,7 @@ export class HelperRest {
699
699
  * }
700
700
  * ```
701
701
  *
702
- * type=**"oauthSamlAssertion"** having auth.options:
702
+ * type=**"oauthSamlBearer"** having auth.options:
703
703
  * ```
704
704
  * {
705
705
  * "options": {
@@ -715,7 +715,7 @@ export class HelperRest {
715
715
  * }
716
716
  * ```
717
717
  *
718
- * type=**"oauthJwtAssertion"** having auth.options:
718
+ * type=**"oauthJwtBearer"** having auth.options:
719
719
  * ```
720
720
  * // Google API - baseUrls automatically set to [https://www.googleapis.com]
721
721
  * {
package/lib/logger.ts CHANGED
@@ -49,7 +49,7 @@ export class Log {
49
49
  )
50
50
  }
51
51
 
52
- private maskSecret = winston.format((info) => {
52
+ private maskSecret = winston.format((info: any) => {
53
53
  // mask json secrets
54
54
  let rePattern = new RegExp(this.reJson, 'i')
55
55
  let msg: string = info.message
@@ -2949,6 +2949,9 @@ export class ScimGateway {
2949
2949
  isHtml = true
2950
2950
  msgObj.content = `<html><body><pre style="font-family: monospace; white-space: pre-wrap;">${msgObj.content}</pre></body></html>`
2951
2951
  }
2952
+ if (!msgObj.to) msgObj.to = ''
2953
+ if (!msgObj.cc) msgObj.cc = ''
2954
+ if (!msgObj.subject) msgObj.subject = 'SCIM Gateway message'
2952
2955
 
2953
2956
  if (authType === 'oauth') {
2954
2957
  if (!this.helperRest) this.helperRest = new HelperRest(this, { entity: { undefined: { connection: this.config.scimgateway.email } } })
@@ -2956,7 +2959,7 @@ export class ScimGateway {
2956
2959
  // Microsoft Exchange Online (ExO) - using Graph API
2957
2960
  const emailMessage: Record<string, any> = {
2958
2961
  message: {
2959
- subject: msgObj.subject ? msgObj.subject : 'SCIM Gateway message',
2962
+ subject: msgObj.subject,
2960
2963
  body: {
2961
2964
  content: msgObj.content,
2962
2965
  contentType: isHtml ? 'HTML' : 'Text',
@@ -2993,16 +2996,13 @@ export class ScimGateway {
2993
2996
  const path = `/users/${msgObj.from}/sendMail`
2994
2997
  try {
2995
2998
  await this.helperRest.doRequest('undefined', 'POST', path, emailMessage)
2996
- logger.debug(`${gwName}[${pluginName}] sendMail subject '${emailMessage.message.subject}' sent to: ${msgObj.to}${(msgObj.cc) ? ',' + msgObj.cc : ''}`)
2999
+ logger.debug(`${gwName}[${pluginName}] sendMail subject '${msgObj.subject}' sent to: ${msgObj.to}${(msgObj.cc) ? ',' + msgObj.cc : ''}`)
2997
3000
  } catch (err: any) {
2998
- logger.error(`${gwName}[${pluginName}] sendMail subject '${emailMessage.message.subject}' sending failed: ${err.message}`)
3001
+ logger.error(`${gwName}[${pluginName}] sendMail subject '${msgObj.subject}' sending failed: ${err.message}`)
2999
3002
  }
3000
3003
  return
3001
3004
  } else if (this.config.scimgateway.email.auth?.options?.serviceAccountKeyFile) {
3002
3005
  // Google Workspace Gmail
3003
- if (!msgObj.to) msgObj.to = ''
3004
- if (!msgObj.cc) msgObj.cc = ''
3005
-
3006
3006
  let mimeMessage = `From: ${msgObj.from}
3007
3007
  To: ${msgObj.to}
3008
3008
  Cc: ${msgObj.cc}
@@ -3016,11 +3016,11 @@ Content-Transfer-Encoding: quoted-printable
3016
3016
  const encodedMessage = btoa(mimeMessage)
3017
3017
  const emailMessage = { raw: encodedMessage }
3018
3018
  const path = `/gmail/v1/users/${msgObj.from}/messages/send`
3019
- try { // using opt connection argument type=oauthJwtAssertion and options scope/subject because we want to keep simplified email.auth.type=oauth and options serviceAccountKeyFile
3020
- await this.helperRest.doRequest('undefined', 'POST', path, emailMessage, null, { connection: { auth: { type: 'oauthJwtAssertion', options: { scope: 'https://www.googleapis.com/auth/gmail.send', subject: msgObj.from } } } })
3021
- logger.debug(`${gwName}[${pluginName}] sendMail subject '${emailMessage}' sent to: ${msgObj.to}${(msgObj.cc) ? ',' + msgObj.cc : ''}`)
3019
+ try { // using opt connection argument type=oauthJwtBearer and options scope/subject because we want to keep simplified email.auth.type=oauth and options serviceAccountKeyFile
3020
+ await this.helperRest.doRequest('undefined', 'POST', path, emailMessage, null, { connection: { auth: { type: 'oauthJwtBearer', options: { scope: 'https://www.googleapis.com/auth/gmail.send', subject: msgObj.from } } } })
3021
+ logger.debug(`${gwName}[${pluginName}] sendMail subject '${msgObj.subject}' sent to: ${msgObj.to}${(msgObj.cc) ? ',' + msgObj.cc : ''}`)
3022
3022
  } catch (err: any) {
3023
- logger.error(`${gwName}[${pluginName}] sendMail subject '${emailMessage}' sending failed: ${err.message}`)
3023
+ logger.error(`${gwName}[${pluginName}] sendMail subject '${msgObj.subject}' sending failed: ${err.message}`)
3024
3024
  }
3025
3025
  return
3026
3026
  }
@@ -3057,15 +3057,15 @@ Content-Transfer-Encoding: quoted-printable
3057
3057
  from: msgObj.from, // sender address
3058
3058
  to: msgObj.to, // list of receivers - comma separated
3059
3059
  cc: msgObj.cc,
3060
- subject: msgObj.subject ? msgObj.subject : 'SCIM Gateway message',
3060
+ subject: msgObj.subject,
3061
3061
  }
3062
3062
 
3063
3063
  if (isHtml) mailOptions.html = msgObj.content
3064
3064
  else mailOptions.text = msgObj.content
3065
3065
 
3066
3066
  transporter.sendMail(mailOptions, function (err) {
3067
- if (err != null) logger.error(`${gwName}[${pluginName}] sendMail subject '${mailOptions.subject}' sending failed: ${err.message}`)
3068
- else logger.debug(`${gwName}[${pluginName}] sendMail subject '${mailOptions.subject}' sent to: ${msgObj.to}${(msgObj.cc) ? ',' + msgObj.cc : ''}`)
3067
+ if (err != null) logger.error(`${gwName}[${pluginName}] sendMail subject '${msgObj.subject}' sending failed: ${err.message}`)
3068
+ else logger.debug(`${gwName}[${pluginName}] sendMail subject '${msgObj.subject}' sent to: ${msgObj.to}${(msgObj.cc) ? ',' + msgObj.cc : ''}`)
3069
3069
  })
3070
3070
  }
3071
3071
 
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "scimgateway",
3
- "version": "5.0.14",
3
+ "version": "5.0.15",
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)",