scimgateway 5.0.4 → 5.0.6

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,11 +16,11 @@ Validated through IdP's:
16
16
 
17
17
  Latest news:
18
18
 
19
- - 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.
19
+ - Major version **v5** marks a shift to native TypeScript support and prioritizes [Bun](https://bun.sh/) over Node.js. This upgrade requires some modifications to existing plugins.
20
20
  - **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
21
- - Authentication PassThrough letting plugin pass authentication directly to endpoint for avoid maintaining secrets at the gateway. Kubernetes health checks and shutdown handler support
21
+ - Authentication PassThrough letting plugin pass authentication directly to endpoint for avoid maintaining secrets at the gateway
22
22
  - Supports OAuth Client Credentials authentication
23
- - Major version **v4.0.0** getUsers() and getGroups() replacing some deprecated methods. No limitations on filtering/sorting. Admin user access can be linked to specific baseEntities. New MongoDB plugin
23
+ - Major version **v4** getUsers() and getGroups() replacing some deprecated methods. No limitations on filtering/sorting. Admin user access can be linked to specific baseEntities. New MongoDB plugin
24
24
  - ipAllowList for restricting access to allowlisted IP addresses or subnets e.g. Azure IP-range
25
25
  - General LDAP plugin configured for Active Directory
26
26
  - [PlugSSO](https://elshaug.xyz/docs/plugsso) using SCIM Gateway
@@ -157,7 +157,7 @@ If internet connection is blocked, we could install on another machine and copy
157
157
 
158
158
  Not needed after a fresh install
159
159
 
160
- The best and easiest way to upgrade is renaming existing scimgateway package folder, create a new one and do a fresh installation. After the installation we copy `index.ts, config and lib folder` (customized plugins) from previous installation to the new installation. You should also read the version history to see custom plugins needs to be updated.
160
+ The best and easiest way to upgrade is renaming existing scimgateway package folder, create a new one and do a fresh installation. After the installation we copy `index.ts, config and lib folder` (customized plugins) from previous installation to the new installation. You should also read the version history to see if custom plugins needs to be updated.
161
161
 
162
162
  Alternatives are:
163
163
 
@@ -208,7 +208,6 @@ Below shows an example of config\plugin-saphana.json
208
208
  "scimgateway": {
209
209
  "port": 8884,
210
210
  "localhostonly": false,
211
- "payloadSize": null,
212
211
  "scim": {
213
212
  "version": "2.0",
214
213
  "skipTypeConvert" : false,
@@ -281,18 +280,19 @@ Below shows an example of config\plugin-saphana.json
281
280
  }
282
281
  },
283
282
  "ipAllowList": [],
284
- "emailOnError": {
285
- "smtp": {
283
+ "email": {
284
+ "auth": {
285
+ "type": "oauth",
286
+ "options": {
287
+ "tenantIdGUID": null,
288
+ "clientId": null,
289
+ "clientSecret": null
290
+ }
291
+ },
292
+ "emailOnError": {
286
293
  "enabled": false,
287
- "host": null,
288
- "port": 587,
289
- "proxy": null,
290
- "authenticate": true,
291
- "username": null,
292
- "password": null,
293
- "sendInterval": 15,
294
- "to": null,
295
- "cc": null
294
+ "from": null,
295
+ "to": null
296
296
  }
297
297
  },
298
298
  "stream": {
@@ -349,15 +349,15 @@ Definitions in `scimgateway` object have fixed attributes, but values can be mod
349
349
 
350
350
  Definitions in `endpoint` object are customized according to our plugin code. Plugin typically need this information for communicating with endpoint
351
351
 
352
- - **port** - Gateway will listen on this port number. Clients (e.g. Provisioning Server) will be using this port number for communicating with the gateway.
352
+ - **port** - Gateway will listen on this port number. Clients (e.g. Provisioning Server) will be using this port number for communicating with the gateway
353
353
 
354
- - **localhostonly** - true or false. False means gateway accepts incoming requests from all clients. True means traffic from only localhost (127.0.0.1) is accepted.
354
+ - **localhostonly** - true or false. False means gateway accepts incoming requests from all clients. True means traffic from only localhost (127.0.0.1) is accepted.
355
355
 
356
- - **payloadSize** - if not defined, default "1mb" will be used. There are cases which large groups could exceed default size and you may want to increase by setting your own size
356
+ - **idleTimeout** - default 120, sets the the number of seconds to wait before timing out a connection due to inactivity
357
357
 
358
- - **scim.version** - "1.1" or "2.0". Default is "2.0".
358
+ - **scim.version** - "1.1" or "2.0". Default is "2.0".
359
359
 
360
- - **scim.skipTypeConvert** - true or false, default false. Multivalue attributes supporting types e.g. emails, phoneNumbers, ims, photos, addresses, entitlements and x509Certificates (but not roles, groups and members) will be become "type converted objects" when sent to modifyUser and createUser. This for simplicity of checking attributes included and also for the endpointMapper method (used by plugin-ldap and plugin-entra-id), e.g.:
360
+ - **scim.skipTypeConvert** - true or false, default false. Multivalue attributes supporting types e.g. emails, phoneNumbers, ims, photos, addresses, entitlements and x509Certificates (but not roles, groups and members) will be become "type converted objects" when sent to modifyUser and createUser. This for simplicity of checking attributes included and also for the endpointMapper method (used by plugin-ldap and plugin-entra-id), e.g.:
361
361
 
362
362
  "emails": {
363
363
  "work": {"value": "jsmith@example.com", "type": "work"},
@@ -375,34 +375,34 @@ Definitions in `endpoint` object are customized according to our plugin code. Pl
375
375
 
376
376
  - **scim.skipMetaLocation** - true or false, default false. If set to true, `meta.location` which contains protocol and hostname from request-url, will be excluded from response e.g. `"{...,meta":{"location":"https://my-company.com/<...>"}}`. If using reverse proxy and not including headers `X-Forwarded-Proto` and `X-Forwarded-Host`, originator will be the proxy and we might not want to expose internal protocol and hostname being used by the proxy request.
377
377
 
378
- - **scim."groupMemberOfUser** - true or false, default false. If body contains groups and groupMemberOfUser=true, groups attribute will remain at user object (groups are member of user) instead of default user member of groups that will use modifyGroup method for maintaining group members.
378
+ - **scim.groupMemberOfUser** - true or false, default false. If body contains groups and groupMemberOfUser=true, groups attribute will remain at user object (groups are member of user) instead of default user member of groups that will use modifyGroup method for maintaining group members.
379
379
 
380
380
  - **scim.usePutSoftSync** - true or false, default false. `PUT /Users/bjensen` will replace the user bjensen with body content. If set to `true`, only PUT body content will be replaced. Any additional existing user attributes and groups supported by plugin will remain as-is.
381
381
 
382
- - **log.loglevel.file** - off, error, info, or debug. Output to plugin-logfile e.g. `logs\plugin-saphana.log`
382
+ - **log.loglevel.file** - off, error, info, or debug. Output to plugin-logfile e.g. `logs\plugin-saphana.log`
383
383
 
384
- - **log.loglevel.console** - off, error, info, or debug. Output to stdout and errors to stderr.
384
+ - **log.loglevel.console** - off, error, info, or debug. Output to stdout and errors to stderr.
385
385
 
386
- - **log.customMasking** - array of attributes to be masked e.g. `"customMasking": ["SSN", "weight"]`. By default SCIM Gateway includes masking of some standard attributes like password.
386
+ - **log.customMasking** - array of attributes to be masked e.g. `"customMasking": ["SSN", "weight"]`. By default SCIM Gateway includes masking of some standard attributes like password.
387
387
 
388
388
  - **auth** - Contains one or more authentication/authorization methods used by clients for accessing gateway - may also include:
389
389
  - **auth.xx.readOnly** - true/false, true gives read only access - only allowing `GET` requests for corresponding admin user
390
390
  - **auth.xx.baseEntities** - array containing one or more `baseEntity` allowed for this user e.g. ["client-a"] - empty array allowing all.
391
- **Methods are disabled by setting corresponding admin user to null or remove methods not used**
391
+ **Methods are disabled by setting corresponding admin user to null or remove methods not used**
392
392
 
393
- - **auth.basic** - Array of one ore more basic authentication objects - Basic Authentication with **username**/**password**. Note, we set a clear text password that will become encrypted when gateway is started.
393
+ - **auth.basic** - Array of one ore more basic authentication objects - Basic Authentication with **username**/**password**. Note, we set a clear text password that will become encrypted when gateway is started.
394
394
 
395
- - **auth.bearerToken** - Array of one or more bearer token objects - Shared token/secret (supported by Entra ID). Clear text value will become encrypted when gateway is started.
395
+ - **auth.bearerToken** - Array of one or more bearer token objects - Shared token/secret (supported by Entra ID). Clear text value will become encrypted when gateway is started.
396
396
 
397
- - **auth.bearerJwtAzure** - Array of one or more JWT used by Azure SyncFabric. **tenantIdGUID** must be set to Entra ID Tenant ID.
397
+ - **auth.bearerJwtAzure** - Array of one or more JWT used by Azure SyncFabric. **tenantIdGUID** must be set to Entra ID Tenant ID.
398
398
 
399
- - **auth.bearerJwt** - Array of one or more standard JWT objects. Using **secret** or **publicKey** for signature verification. publicKey should be set to the filename of public key or certificate pem-file located in `<package-root>\config\certs` or absolute path being used. Clear text secret will become encrypted when gateway is started. **options.issuer** is mandatory. Other options may also be included according to jsonwebtoken npm package definition.
399
+ - **auth.bearerJwt** - Array of one or more standard JWT objects. Using **secret** or **publicKey** for signature verification. publicKey should be set to the filename of public key or certificate pem-file located in `<package-root>\config\certs` or absolute path being used. Clear text secret will become encrypted when gateway is started. **options.issuer** is mandatory. Other options may also be included according to jsonwebtoken npm package definition.
400
400
 
401
- - **auth.bearerOAuth** - Array of one or more Client Credentials OAuth configuration objects. **`client_id`** and **`client_secret`** are mandatory. client_secret value will become encrypted when gateway is started. OAuth token request url is **/oauth/token** e.g. http://localhost:8880/oauth/token
401
+ - **auth.bearerOAuth** - Array of one or more Client Credentials OAuth configuration objects. **`client_id`** and **`client_secret`** are mandatory. client_secret value will become encrypted when gateway is started. OAuth token request url is **/oauth/token** e.g. http://localhost:8880/oauth/token
402
402
 
403
- - **auth.passThrough** - Setting **auth.passThrough.enabled=true** will bypass SCIM Gateway authentication. Gateway will instead pass ctx containing authentication header to the plugin. Plugin could then use this information for endpoint authentication and we don't have any password/token stored at the gateway. Note, this also requires plugin binary having `scimgateway.authPassThroughAllowed = true` and endpoint logic for handling/passing ctx.request.header.authorization
403
+ - **auth.passThrough** - Setting **auth.passThrough.enabled=true** will bypass SCIM Gateway authentication. Gateway will instead pass ctx containing authentication header to the plugin. Plugin could then use this information for endpoint authentication and we don't have any password/token stored at the gateway. Note, this also requires plugin binary having `scimgateway.authPassThroughAllowed = true` and endpoint logic for handling/passing ctx.request.header.authorization
404
404
 
405
- - **certificate** - If not using TLS certificate, set "key", "cert" and "ca" to **null**. When using TLS, "key" and "cert" have to be defined with the filename corresponding to the primary-key and public-certificate. Both files must be located in the `<package-root>\config\certs` directory unless absolute path being defined e.g:
405
+ - **certificate** - If not using TLS certificate, set "key", "cert" and "ca" to **null**. When using TLS, "key" and "cert" have to be defined with the filename corresponding to the primary-key and public-certificate. Both files must be located in the `<package-root>\config\certs` directory unless absolute path being defined e.g:
406
406
 
407
407
  "certificate": {
408
408
  "key": "key.pem",
@@ -436,23 +436,34 @@ Definitions in `endpoint` object are customized according to our plugin code. Pl
436
436
  "2603:1056:2000::/48",
437
437
  "2603:1057:2::/48"
438
438
  ]
439
-
440
- - **emailOnError** - Contains configuration for sending error notifications by email. Note, only the first error will be sent until sendInterval have passed
441
- - **emailOnError.smtp.enabled** - true or false, value set to true will enable email notifications
442
- - **emailOnError.smtp.host** - Mailserver e.g. "smtp.office365.com"
443
- - **emailOnError.smtp.port** - Port used by mailserver e.g. 587, 25 or 465
444
- - **emailOnError.smtp.proxy** - If using mailproxy e.g. "http://proxy-host:1234"
445
- - **emailOnError.smtp.authenticate** - true or false, set to true will use username/password authentication
446
- - **emailOnError.smtp.username** - Mail account for authentication and also the sender of the email, e.g. "user@outlook.com"
447
- - **emailOnError.smtp.password** - Mail account password
448
- - **emailOnError.smtp.sendInterval** - Mail notifications on error are deferred until sendInterval **minutes** have passed since the last notification. Default 15 minutes
449
- - **emailOnError.smtp.to** - Comma separated list of recipients email addresses e.g: "someone@example.com"
450
- - **emailOnError.smtp.cc** - Comma separated list of cc email addresses
439
+ - **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
440
+ - **email.host** - Mailserver e.g. "smtp.gmail.com" - mandatory when not using tenantIdGUID (Microsoft)
441
+ - **email.port** - Port used by mailserver e.g. 587, 25 or 465 - mandatory when not using tenantIdGUID (Microsoft)
442
+ - **email.auth** - Authentication configuration
443
+ - **email.auth.type** - `basic` or `oauth`
444
+ - **email.auth.options** - Authentication configuration options - note, different options for type basic and oauth
445
+ - **email.auth.options.username (basic)** - Mail account for authentication normally same as sender of the email, e.g. "user@gmail.com"
446
+ - **email.auth.options.password (basic)** - Mail account password
447
+ - **email.auth.options.tenantIdGUID (oauth)** - Entra ID tenant id, mandatory/recommended when using Microsoft Exchange Online
448
+ - **email.auth.options.tokenUrl (oauth)** - Token endpoint, mandatory when not using tenantIdGUID (Microsoft Exchange Online)
449
+ - **email.auth.options.clientId (oauth)** - Client ID
450
+ - **email.auth.options.clientSecret (oauth)** - Client Secret
451
+ - **email.proxy** - Proxy configuration if using mailproxy
452
+ - **email.proxy.host** - Proxy host e.g. `http://proxy-host:1234`
453
+ - **email.proxy.username** - username if authentication is required
454
+ - **email.proxy.password** - password if authentication is required
455
+ - **email.emailOnError** - Contains configuration for sending error notifications by email. Note, only the first error will be sent until sendInterval have passed
456
+ - **email.emailOnError.enabled** - true or false, value set to true will enable email notifications
457
+ - **email.emailOnError.sendInterval** - Default 15. Mail notifications on error are deferred until sendInterval **minutes** have passed since the last notification.
458
+ - **email.emailOnError.from** - Sender email addresses e.g: "noreply@example.com", note must correspond with email.auth.options being used and mailserver configuration
459
+ - **email.emailOnError.to** - Comma separated list of recipients email addresses e.g: "someone@example.com"
460
+ - **email.emailOnError.cc** - Optional comma separated list of cc mail addresses
461
+ - **email.emailOnError.subject** - Optional mail subject, default `SCIM Gateway error message`
451
462
 
452
463
  - **stream** - See [SCIM Stream](https://elshaug.xyz/docs/scim-stream) for configuration details
453
464
 
454
- - **endpoint** - Contains endpoint specific configuration according to our **plugin code**.
455
-
465
+ - **endpoint** - Contains endpoint specific configuration according to our **plugin code**.
466
+
456
467
  #### Configuration notes
457
468
 
458
469
  - Custom Schemas, ServiceProviderConfig and ResourceType can be used if `./lib/scimdef-v2.json or scimdef-v1.json` exists. Original scimdef-v2.json/scimdef-v1.json can be copied from node_modules/scimgateway/lib to your plugin/lib and customized.
@@ -1086,6 +1097,21 @@ MIT © [Jarle Elshaug](https://www.elshaug.xyz)
1086
1097
 
1087
1098
  ## Change log
1088
1099
 
1100
+ ### v5.0.6
1101
+
1102
+ [Improved]
1103
+
1104
+ - new configuration option: `scimgateway.idleTimeout` default 120, sets the the number of seconds to wait before timing out a connection due to inactivity
1105
+ - new configuration option: `scimgateway.email` replacing legacy `scimgateway.emailOnError` (legacy still supported). Email now support oauth authentication configuration which is default and recommended for Microsoft Exchange Online.
1106
+ - removed configuration option: `scimgateway.payloadSize` Bun using default maxRequestBodySize 128MB
1107
+ - plugin may send email using method scimgateway.sendMail()
1108
+
1109
+ ### v5.0.5
1110
+
1111
+ [Fixed]
1112
+
1113
+ - plugin-ldap, dn special character not correct for ascii code 128(dec)/80(hex)
1114
+
1089
1115
  ### v5.0.4
1090
1116
 
1091
1117
  [Improved]
@@ -1098,7 +1124,7 @@ MIT © [Jarle Elshaug](https://www.elshaug.xyz)
1098
1124
 
1099
1125
  - unauthorized connection when using configuration bearerJwtAzure
1100
1126
 
1101
- [Improved]
1127
+ [Improved]
1102
1128
 
1103
1129
  - minor type definition cosmetics
1104
1130
 
@@ -2,7 +2,6 @@
2
2
  "scimgateway": {
3
3
  "port": 8890,
4
4
  "localhostonly": false,
5
- "payloadSize": null,
6
5
  "scim": {
7
6
  "version": "2.0",
8
7
  "skipTypeConvert": false,
@@ -75,18 +74,19 @@
75
74
  }
76
75
  },
77
76
  "ipAllowList": [],
78
- "emailOnError": {
79
- "smtp": {
77
+ "email": {
78
+ "auth": {
79
+ "type": "oauth",
80
+ "options": {
81
+ "tenantIdGUID": null,
82
+ "clientId": null,
83
+ "clientSecret": null
84
+ }
85
+ },
86
+ "emailOnError": {
80
87
  "enabled": false,
81
- "host": null,
82
- "port": 587,
83
- "proxy": null,
84
- "authenticate": true,
85
- "username": null,
86
- "password": null,
87
- "sendInterval": 15,
88
- "to": null,
89
- "cc": null
88
+ "from": null,
89
+ "to": null
90
90
  }
91
91
  },
92
92
  "stream": {
@@ -2,7 +2,6 @@
2
2
  "scimgateway": {
3
3
  "port": 8881,
4
4
  "localhostonly": false,
5
- "payloadSize": null,
6
5
  "scim": {
7
6
  "version": "2.0",
8
7
  "customSchema": null,
@@ -75,18 +74,56 @@
75
74
  }
76
75
  },
77
76
  "ipAllowList": [],
78
- "emailOnError": {
79
- "smtp": {
77
+ "email": {
78
+ "auth": {
79
+ "type": "oauth",
80
+ "options": {
81
+ "tenantIdGUID": null,
82
+ "clientId": null,
83
+ "clientSecret": null
84
+ }
85
+ },
86
+ "emailOnError": {
87
+ "enabled": false,
88
+ "from": null,
89
+ "to": null
90
+ }
91
+ },
92
+ "stream": {
93
+ "baseUrls": [],
94
+ "certificate": {
95
+ "ca": null
96
+ },
97
+ "subscriber": {
80
98
  "enabled": false,
81
- "host": null,
82
- "port": 587,
83
- "proxy": null,
84
- "authenticate": true,
85
- "username": null,
86
- "password": null,
87
- "sendInterval": 15,
88
- "to": null,
89
- "cc": null
99
+ "entity": {
100
+ "undefined": {
101
+ "nats": {
102
+ "tenant": null,
103
+ "subject": null,
104
+ "jwt": null,
105
+ "secret": null
106
+ },
107
+ "deleteUserOnLastGroupRoleRemoval": false,
108
+ "skipConvertRolesToGroups": false,
109
+ "generateUserPassword": false,
110
+ "modifyOnly": false,
111
+ "replaceDomains": []
112
+ }
113
+ }
114
+ },
115
+ "publisher": {
116
+ "enabled": false,
117
+ "entity": {
118
+ "undefined": {
119
+ "nats": {
120
+ "tenant": null,
121
+ "subject": null,
122
+ "jwt": null,
123
+ "secret": null
124
+ }
125
+ }
126
+ }
90
127
  }
91
128
  }
92
129
  },
@@ -2,7 +2,6 @@
2
2
  "scimgateway": {
3
3
  "port": 8883,
4
4
  "localhostonly": false,
5
- "payloadSize": null,
6
5
  "scim": {
7
6
  "version": "2.0",
8
7
  "skipTypeConvert": false,
@@ -75,18 +74,19 @@
75
74
  }
76
75
  },
77
76
  "ipAllowList": [],
78
- "emailOnError": {
79
- "smtp": {
77
+ "email": {
78
+ "auth": {
79
+ "type": "oauth",
80
+ "options": {
81
+ "tenantIdGUID": null,
82
+ "clientId": null,
83
+ "clientSecret": null
84
+ }
85
+ },
86
+ "emailOnError": {
80
87
  "enabled": false,
81
- "host": null,
82
- "port": 587,
83
- "proxy": null,
84
- "authenticate": true,
85
- "username": null,
86
- "password": null,
87
- "sendInterval": 15,
88
- "to": null,
89
- "cc": null
88
+ "from": null,
89
+ "to": null
90
90
  }
91
91
  },
92
92
  "stream": {
@@ -275,4 +275,4 @@
275
275
  }
276
276
  }
277
277
  }
278
- }
278
+ }
@@ -2,7 +2,6 @@
2
2
  "scimgateway": {
3
3
  "port": 8880,
4
4
  "localhostonly": false,
5
- "payloadSize": null,
6
5
  "scim": {
7
6
  "version": "2.0",
8
7
  "skipTypeConvert": false,
@@ -75,18 +74,19 @@
75
74
  }
76
75
  },
77
76
  "ipAllowList": [],
78
- "emailOnError": {
79
- "smtp": {
77
+ "email": {
78
+ "auth": {
79
+ "type": "oauth",
80
+ "options": {
81
+ "tenantIdGUID": null,
82
+ "clientId": null,
83
+ "clientSecret": null
84
+ }
85
+ },
86
+ "emailOnError": {
80
87
  "enabled": false,
81
- "host": null,
82
- "port": 587,
83
- "proxy": null,
84
- "authenticate": true,
85
- "username": null,
86
- "password": null,
87
- "sendInterval": 15,
88
- "to": null,
89
- "cc": null
88
+ "from": null,
89
+ "to": null
90
90
  }
91
91
  },
92
92
  "stream": {
@@ -2,7 +2,6 @@
2
2
  "scimgateway": {
3
3
  "port": 8885,
4
4
  "localhostonly": false,
5
- "payloadSize": null,
6
5
  "scim": {
7
6
  "version": "2.0",
8
7
  "skipTypeConvert": false,
@@ -81,18 +80,19 @@
81
80
  }
82
81
  },
83
82
  "ipAllowList": [],
84
- "emailOnError": {
85
- "smtp": {
83
+ "email": {
84
+ "auth": {
85
+ "type": "oauth",
86
+ "options": {
87
+ "tenantIdGUID": null,
88
+ "clientId": null,
89
+ "clientSecret": null
90
+ }
91
+ },
92
+ "emailOnError": {
86
93
  "enabled": false,
87
- "host": null,
88
- "port": 587,
89
- "proxy": null,
90
- "authenticate": true,
91
- "username": null,
92
- "password": null,
93
- "sendInterval": 15,
94
- "to": null,
95
- "cc": null
94
+ "from": null,
95
+ "to": null
96
96
  }
97
97
  },
98
98
  "stream": {
@@ -151,4 +151,4 @@
151
151
  }
152
152
  }
153
153
  }
154
- }
154
+ }
@@ -2,7 +2,6 @@
2
2
  "scimgateway": {
3
3
  "port": 8888,
4
4
  "localhostonly": false,
5
- "payloadSize": null,
6
5
  "scim": {
7
6
  "version": "2.0",
8
7
  "skipTypeConvert": false,
@@ -75,18 +74,19 @@
75
74
  }
76
75
  },
77
76
  "ipAllowList": [],
78
- "emailOnError": {
79
- "smtp": {
77
+ "email": {
78
+ "auth": {
79
+ "type": "oauth",
80
+ "options": {
81
+ "tenantIdGUID": null,
82
+ "clientId": null,
83
+ "clientSecret": null
84
+ }
85
+ },
86
+ "emailOnError": {
80
87
  "enabled": false,
81
- "host": null,
82
- "port": 587,
83
- "proxy": null,
84
- "authenticate": true,
85
- "username": null,
86
- "password": null,
87
- "sendInterval": 15,
88
- "to": null,
89
- "cc": null
88
+ "from": null,
89
+ "to": null
90
90
  }
91
91
  },
92
92
  "stream": {
@@ -133,18 +133,18 @@
133
133
  "authentication": {
134
134
  "type": "default",
135
135
  "options": {
136
- "userName": "sa",
136
+ "userName": "scimgateway",
137
137
  "password": "password"
138
138
  }
139
139
  },
140
140
  "options": {
141
141
  "instanceName": "",
142
142
  "port": 1433,
143
- "database": "MyDatabase",
143
+ "database": "scimgateway",
144
144
  "useColumnNames": true,
145
145
  "rowCollectionOnRequestCompletion": true,
146
146
  "encrypt": false
147
147
  }
148
148
  }
149
149
  }
150
- }
150
+ }
@@ -2,7 +2,6 @@
2
2
  "scimgateway": {
3
3
  "port": 8884,
4
4
  "localhostonly": false,
5
- "payloadSize": null,
6
5
  "scim": {
7
6
  "version": "2.0",
8
7
  "skipTypeConvert": false,
@@ -75,18 +74,19 @@
75
74
  }
76
75
  },
77
76
  "ipAllowList": [],
78
- "emailOnError": {
79
- "smtp": {
77
+ "email": {
78
+ "auth": {
79
+ "type": "oauth",
80
+ "options": {
81
+ "tenantIdGUID": null,
82
+ "clientId": null,
83
+ "clientSecret": null
84
+ }
85
+ },
86
+ "emailOnError": {
80
87
  "enabled": false,
81
- "host": null,
82
- "port": 587,
83
- "proxy": null,
84
- "authenticate": true,
85
- "username": null,
86
- "password": null,
87
- "sendInterval": 15,
88
- "to": null,
89
- "cc": null
88
+ "from": null,
89
+ "to": null
90
90
  }
91
91
  },
92
92
  "stream": {
@@ -134,4 +134,4 @@
134
134
  "password": "password",
135
135
  "saml_provider": "saml_provider_name"
136
136
  }
137
- }
137
+ }
@@ -2,7 +2,6 @@
2
2
  "scimgateway": {
3
3
  "port": 8886,
4
4
  "localhostonly": false,
5
- "payloadSize": null,
6
5
  "scim": {
7
6
  "version": "2.0",
8
7
  "skipTypeConvert": false,
@@ -75,18 +74,19 @@
75
74
  }
76
75
  },
77
76
  "ipAllowList": [],
78
- "emailOnError": {
79
- "smtp": {
77
+ "email": {
78
+ "auth": {
79
+ "type": "oauth",
80
+ "options": {
81
+ "tenantIdGUID": null,
82
+ "clientId": null,
83
+ "clientSecret": null
84
+ }
85
+ },
86
+ "emailOnError": {
80
87
  "enabled": false,
81
- "host": null,
82
- "port": 587,
83
- "proxy": null,
84
- "authenticate": true,
85
- "username": null,
86
- "password": null,
87
- "sendInterval": 15,
88
- "to": null,
89
- "cc": null
88
+ "from": null,
89
+ "to": null
90
90
  }
91
91
  },
92
92
  "stream": {
@@ -2,7 +2,6 @@
2
2
  "scimgateway": {
3
3
  "port": 8882,
4
4
  "localhostonly": false,
5
- "payloadSize": null,
6
5
  "scim": {
7
6
  "version": "2.0",
8
7
  "skipTypeConvert": false,
@@ -75,18 +74,19 @@
75
74
  }
76
75
  },
77
76
  "ipAllowList": [],
78
- "emailOnError": {
79
- "smtp": {
77
+ "email": {
78
+ "auth": {
79
+ "type": "oauth",
80
+ "options": {
81
+ "tenantIdGUID": null,
82
+ "clientId": null,
83
+ "clientSecret": null
84
+ }
85
+ },
86
+ "emailOnError": {
80
87
  "enabled": false,
81
- "host": null,
82
- "port": 587,
83
- "proxy": null,
84
- "authenticate": true,
85
- "username": null,
86
- "password": null,
87
- "sendInterval": 15,
88
- "to": null,
89
- "cc": null
88
+ "from": null,
89
+ "to": null
90
90
  }
91
91
  },
92
92
  "stream": {
@@ -4,7 +4,7 @@ import { Buffer } from 'node:buffer'
4
4
  import fs from 'node:fs'
5
5
  import querystring from 'querystring'
6
6
  import * as utils from './utils.ts'
7
- import type ScimGateway from 'scimgateway'
7
+ import ScimGateway from 'scimgateway'
8
8
 
9
9
  /**
10
10
  * HelperRest includes function doRequest() for doing REST calls
@@ -14,12 +14,16 @@ export class HelperRest {
14
14
  private _serviceClient: Record<string, any> = {}
15
15
  private config_entity: any
16
16
  private scimgateway: ScimGateway
17
+ private idleTimeout: number
17
18
  private graphUrl = 'https://graph.microsoft.com/beta' // beta instead of 'v1.0' gives all user attributes when no $select
18
19
 
19
- constructor(scimgateway: ScimGateway) {
20
+ constructor(scimgateway: ScimGateway, optionalEntities?: Record<string, any>) {
21
+ if (!(scimgateway instanceof ScimGateway)) throw new Error('HelperRest initialization error: argument scimgateway is not of type ScimGateway')
20
22
  this.scimgateway = scimgateway
21
- const config = scimgateway.getConfig()
22
- this.config_entity = config.entity
23
+ this.idleTimeout = (scimgateway as any)?.config?.scimgateway.idleTimeout || 120
24
+ this.idleTimeout = this.idleTimeout - 1
25
+ if (optionalEntities && optionalEntities.entity) this.config_entity = utils.copyObj(optionalEntities.entity)
26
+ else this.config_entity = utils.copyObj(scimgateway.getConfig())?.entity
23
27
  let entityFound = false
24
28
  let connectionFound = false
25
29
  for (const baseEntity in this.config_entity) {
@@ -31,15 +35,12 @@ export class HelperRest {
31
35
  }
32
36
  }
33
37
  connectionFound = true
34
- if (!this.config_entity[baseEntity].connection.baseUrls
35
- || !Array.isArray(this.config_entity[baseEntity].connection.baseUrls)
36
- || this.config_entity[baseEntity].connection.baseUrls.length < 1) {
37
- throw new Error('HelperRest initialization error: missing configuration \'endpoint.entity.<name>.connection.baseUrls\'')
38
- }
39
38
  }
40
39
  }
41
- if (!entityFound) throw new Error('HelperRest initialization error: missing configuration \'endpoint.entity.<name>\'')
42
- if (!connectionFound) throw new Error('HelperRest initialization error: missing configuration \'endpoint.entity.<name>.connection\'')
40
+ let errMsg = ''
41
+ if (!entityFound) errMsg = 'HelperRest initialization error: missing configuration \'endpoint.entity.<name>\''
42
+ else if (!connectionFound) errMsg = 'HelperRest initialization error: missing configuration \'endpoint.entity.<name>.connection\''
43
+ if (errMsg) this.scimgateway.logError('undefined', errMsg)
43
44
  }
44
45
 
45
46
  /**
@@ -68,12 +69,12 @@ export class HelperRest {
68
69
  }
69
70
 
70
71
  /**
71
- * getAccessToken returns oauth accesstoken
72
+ * getAccessToken returns oauth accesstoken object
72
73
  * @param baseEntity
73
74
  * @param ctx
74
- * @returns oauth accesstoken
75
+ * @returns oauth accesstoken object
75
76
  */
76
- private async getAccessToken(baseEntity: string, ctx: Record<string, any> | undefined) {
77
+ public async getAccessToken(baseEntity: string, ctx?: Record<string, any> | undefined) { // public in case token is needed for other logic e.g. sending mail
77
78
  await this.lock.acquire()
78
79
  const clientIdentifier = this.getClientIdentifier(ctx)
79
80
  const d = Math.floor(Date.now() / 1000) // seconds (unix time)
@@ -417,7 +418,7 @@ export class HelperRest {
417
418
  } else delete options.headers['Content-Type']
418
419
  const controller = new AbortController()
419
420
  const signal = controller.signal
420
- const timeout = setTimeout(() => controller.abort(), options.abortTimeout ? options.abortTimeout * 1000 : 120 * 1000) // 120 seconds default abort timeout
421
+ const timeout = setTimeout(() => controller.abort(), options.abortTimeout ? options.abortTimeout * 1000 : this.idleTimeout * 1000) // 120 seconds default abort timeout
421
422
  options.signal = signal
422
423
  const url = `${options.protocol}//${options.host}${options.port ? ':' + options.port : ''}${options.path}`
423
424
  // execute request
@@ -486,7 +487,7 @@ export class HelperRest {
486
487
  if (!retryCount) retryCount = 0
487
488
  let urlObj
488
489
  try { urlObj = new URL(path) } catch (err) { void 0 }
489
- if (!urlObj && (err.code === 'ECONNREFUSED' || err.code === 'ENOTFOUND' || err.code === 'ETIMEDOUT' || retryAfter)) {
490
+ if (!urlObj && (err.code === 'ECONNREFUSED' || err.code === 'ENOTFOUND' || err.code === 'ABORT_ERR' || err.code === 'ETIMEDOUT' || retryAfter)) {
490
491
  if (retryAfter) {
491
492
  this.scimgateway.logDebug(baseEntity, `doRequest ${method} ${path} throttle/ratelimit error - awaiting ${retryAfter} seconds before automatic retry`)
492
493
  await new Promise(resolve => setTimeout(function () {
package/lib/logger.ts CHANGED
@@ -4,9 +4,7 @@
4
4
  // Author: Jarle Elshaug
5
5
  // ==============================================================
6
6
 
7
- // import * as winston from 'winston' // level: silly=0(lowest), debug=1, verbose=2, info=3, warn=4, error=5(highest)
8
- // import winston, { Logger } from 'winston'
9
- import winston, { Logger } from 'winston'
7
+ import winston, { Logger } from 'winston' // level: silly=0, debug=1, verbose=2, info=3, warn=4, error=5
10
8
 
11
9
  // Wrong time if not setting timestamp. Timezone did not work.
12
10
  // moment-timezone is also an alternative to the timestamp() function.
@@ -1452,7 +1452,7 @@ const doRequest = async (baseEntity: string, method: string, base: any, options:
1452
1452
  obj.dn = obj.dn.replace(/\\\\/g, '__') // temp
1453
1453
  let conv = obj.dn.replace(/\\([0-9A-Fa-f]{2})/g, (_: any, hex: any) => {
1454
1454
  const intAscii = parseInt(hex, 16)
1455
- if (intAscii > 128) { // extended ascii - will be unescaped by decodeURIComponent
1455
+ if (intAscii > 127) { // extended ascii - will be unescaped by decodeURIComponent
1456
1456
  return '%' + hex
1457
1457
  } else { // use character escape
1458
1458
  return '\\' + String.fromCharCode(intAscii)
@@ -27,6 +27,7 @@ import * as utils from './utils.ts'
27
27
  import * as utilsScim from './utils-scim.ts'
28
28
  import * as stream from './scim-stream.js'
29
29
  export * from './helper-rest.ts'
30
+ import { HelperRest } from './helper-rest.ts'
30
31
 
31
32
  export class ScimGateway {
32
33
  private config: any
@@ -39,6 +40,8 @@ export class ScimGateway {
39
40
  private getMemberOf: any
40
41
  private getAppRoles: any
41
42
  private pub: any
43
+ // @ts-expect-error: has no initializer
44
+ private helperRest: HelperRest
42
45
  /** pluginName is the name of plugin e.g., plugin-loki */
43
46
  readonly pluginName: string
44
47
  /** configDir is full path to plugin ./config directory */
@@ -402,12 +405,50 @@ export class ScimGateway {
402
405
  this.config.scimgateway.auth.oauthTokenStore = {}
403
406
  if (!this.config.scimgateway.certificate) this.config.scimgateway.certificate = {}
404
407
  if (!this.config.scimgateway.certificate.pfx) this.config.scimgateway.certificate.pfx = {}
405
- if (!this.config.scimgateway.emailOnError) this.config.scimgateway.emailOnError = {}
406
- if (!this.config.scimgateway.emailOnError.smtp) this.config.scimgateway.emailOnError.smtp = {}
408
+
409
+ if (!this.config.scimgateway.email) this.config.scimgateway.email = {}
410
+ if (!this.config.scimgateway.email.auth) this.config.scimgateway.email.auth = {}
411
+ if (!this.config.scimgateway.email.auth.options) this.config.scimgateway.email.auth.options = {}
412
+ if (!this.config.scimgateway.email.emailOnError) this.config.scimgateway.email.emailOnError = {}
413
+
407
414
  if (!this.config.scimgateway.stream) this.config.scimgateway.stream = {}
408
415
  if (!this.config.scimgateway.stream.subscriber) this.config.scimgateway.stream.subscriber = {}
409
416
  if (!this.config.scimgateway.stream.publisher) this.config.scimgateway.stream.publisher = {}
410
417
 
418
+ // start - legacy support
419
+ if (!this.config.scimgateway.emailOnError) this.config.scimgateway.emailOnError = {}
420
+ if (!this.config.scimgateway.emailOnError.smtp) this.config.scimgateway.emailOnError.smtp = {}
421
+ if (this.config.scimgateway.emailOnError.smtp.host) {
422
+ this.config.scimgateway.email.host = this.config.scimgateway.emailOnError.smtp.host
423
+ }
424
+ if (this.config.scimgateway.emailOnError.smtp.port) {
425
+ this.config.scimgateway.email.port = this.config.scimgateway.emailOnError.smtp.port
426
+ }
427
+ if (this.config.scimgateway.emailOnError.smtp.proxy) {
428
+ this.config.scimgateway.email.proxy = this.config.scimgateway.emailOnError.smtp.proxy
429
+ }
430
+ if (this.config.scimgateway.emailOnError.smtp.username) {
431
+ this.config.scimgateway.email.emailOnError.from = this.config.scimgateway.emailOnError.smtp.username
432
+ this.config.scimgateway.email.auth.options.username = this.config.scimgateway.emailOnError.smtp.username
433
+ }
434
+ if (this.config.scimgateway.emailOnError.smtp.password) {
435
+ this.config.scimgateway.email.auth.options.password = this.config.scimgateway.emailOnError.smtp.password
436
+ this.config.scimgateway.email.auth.type = 'basic'
437
+ }
438
+ if (this.config.scimgateway.emailOnError.smtp.enabled) {
439
+ this.config.scimgateway.email.emailOnError.enabled = this.config.scimgateway.emailOnError.smtp.enabled
440
+ }
441
+ if (this.config.scimgateway.emailOnError.smtp.sendInterval) {
442
+ this.config.scimgateway.email.emailOnError.sendInterval = this.config.scimgateway.emailOnError.smtp.sendInterval
443
+ }
444
+ if (this.config.scimgateway.emailOnError.smtp.to) {
445
+ this.config.scimgateway.email.emailOnError.to = this.config.scimgateway.emailOnError.smtp.to
446
+ }
447
+ if (this.config.scimgateway.emailOnError.smtp.cc) {
448
+ this.config.scimgateway.email.emailOnError.cc = this.config.scimgateway.emailOnError.smtp.cc
449
+ }
450
+ // end - legacy support
451
+
411
452
  if (this.config.scimgateway.ipAllowList && Array.isArray(this.config.scimgateway.ipAllowList) && this.config.scimgateway.ipAllowList.length > 0) {
412
453
  ipAllowListChecker = createChecker(this.config.scimgateway.ipAllowList)
413
454
  }
@@ -896,7 +937,7 @@ export class ScimGateway {
896
937
  const baseEntity = ctx.routeObj.baseEntity
897
938
  const id = decodeURIComponent(path.basename(ctx.routeObj.id || '', '.json')) // supports <id>.json
898
939
 
899
- if (!id || id.includes('/')) {
940
+ if (!id) {
900
941
  ctx.response.status = 500
901
942
  const err = new Error('missing id')
902
943
  const [e, customErrorCode] = utilsScim.jsonErr(this.config.scimgateway.scim.version, pluginName, ctx.response.status, err)
@@ -2119,7 +2160,7 @@ export class ScimGateway {
2119
2160
  const attributes = ['id', 'displayName']
2120
2161
  logger.debug(`${gwName}[${pluginName}][${baseEntity}] calling "${handler.groups.getMethod}" and awaiting result - groups to be included`)
2121
2162
  res = await (this as any)[handler.groups.getMethod](baseEntity, ob, attributes, ctxPassThrough)
2122
- } catch (err) { } // ignore errors
2163
+ } catch (err) { void 0 }
2123
2164
  if (res && res.Resources && Array.isArray(res.Resources) && res.Resources.length > 0) {
2124
2165
  for (let i = 0; i < res.Resources.length; i++) {
2125
2166
  if (!res.Resources[i].id) continue
@@ -2358,14 +2399,14 @@ export class ScimGateway {
2358
2399
  hostname = 'localhost'
2359
2400
  }
2360
2401
  try {
2361
- // using fs.readFileSync() instead of Bun.file() for nodejs compability
2402
+ // using fs.readFileSync() instead of Bun.file() for nodejs compability
2362
2403
  if (this.config.scimgateway?.certificate?.key && this.config.scimgateway?.certificate?.cert) {
2363
- // TLS
2404
+ // TLS
2364
2405
  tls.key = this.config.scimgateway.certificate.key ? fs.readFileSync(this.config.scimgateway.certificate.key) : undefined
2365
2406
  tls.cert = this.config.scimgateway.certificate.cert ? fs.readFileSync(this.config.scimgateway.certificate.cert) : undefined
2366
- // loading tls.ca would require client certificates to be used
2407
+ // loading tls.ca would require client certificates to be used
2367
2408
  } else if (this.config.scimgateway?.certificate?.pfx && this.config.scimgateway?.certificate?.pfx?.bundle) {
2368
- // TLS PFX / PKCS#12
2409
+ // TLS PFX / PKCS#12
2369
2410
  tls.pfx = this.config.scimgateway.certificate.pfx.bundle ? fs.readFileSync(this.config.scimgateway.certificate.pfx.bundle) : undefined
2370
2411
  tls.passphrase = this.config.scimgateway.certificate.pfx.password ? utils.getSecret('scimgateway.certificate.pfx.password', this.configFile) : undefined
2371
2412
  }
@@ -2439,6 +2480,7 @@ export class ScimGateway {
2439
2480
  server = Bun.serve({
2440
2481
  port: this.config.scimgateway.port,
2441
2482
  reusePort: false,
2483
+ idleTimeout: this.config.scimgateway.idleTimeout || 120,
2442
2484
  hostname, // hostname === 'localhost' ? hostname : undefined, // bun defaults to '0.0.0.0', but using '0.0.0.0.' or other ip like '127.0.0.1' becomes extremly slow - bun bug
2443
2485
  tls,
2444
2486
  async fetch(req, srv) {
@@ -2583,49 +2625,27 @@ export class ScimGateway {
2583
2625
  logger.setLoglevelConsole(this.config?.scimgateway?.log?.loglevel?.console) // revert temporary info console loglevel, use config
2584
2626
 
2585
2627
  logger.setEmailOnError(async (msg: string) => { // logger sending email on error
2586
- if (!this.config.scimgateway.emailOnError || !this.config.scimgateway.emailOnError.smtp || !(this.config.scimgateway.emailOnError.smtp.enabled === true) || isMailLock) return null // not sending mail
2628
+ if (!(this.config.scimgateway.email.emailOnError.enabled === true) || isMailLock) return null // not sending mail
2587
2629
  isMailLock = true
2588
2630
 
2589
2631
  setTimeout(function () { // release lock after "sendInterval" minutes
2590
2632
  isMailLock = false
2591
- }, (this.config.scimgateway.emailOnError.smtp.sendInterval || 15) * 1000 * 60)
2592
-
2593
- const bodyHtml = `<html><body>
2594
- <p>${msg}</p>
2595
- <br>
2596
- <p><strong>This is an automatically generated email - please do NOT reply to this email or forward to others</strong></p>
2597
- </body></html>`
2598
-
2599
- const smtpConfig: { [key: string]: any } = {
2600
- host: this.config.scimgateway.emailOnError.smtp.host, // e.g. smtp.office365.com
2601
- port: this.config.scimgateway.emailOnError.smtp.port || 587,
2602
- proxy: this.config.scimgateway.emailOnError.smtp.proxy || null,
2603
- secure: (this.config.scimgateway.emailOnError.smtp.port === 465), // false on 25/587
2604
- tls: { ciphers: 'TLSv1.2' },
2605
- }
2606
- if (this.config.scimgateway.emailOnError.smtp.authenticate) {
2607
- smtpConfig.auth = {}
2608
- smtpConfig.auth.user = this.config.scimgateway.emailOnError.smtp.username
2609
- smtpConfig.auth.pass = this.config.scimgateway.emailOnError.smtp.password
2610
- }
2611
-
2612
- const transporter = nodemailer.createTransport(smtpConfig)
2613
- const mailOptions = {
2614
- from: this.config.scimgateway.emailOnError.smtp.username, // sender address
2615
- to: this.config.scimgateway.emailOnError.smtp.to, // list of receivers - comma separated
2616
- cc: this.config.scimgateway.emailOnError.smtp.cc,
2617
- subject: 'SCIM Gateway error message',
2618
- html: bodyHtml, // 'text': bodyText
2619
- }
2620
-
2621
- const smtp_to = this.config.scimgateway.emailOnError.smtp.to
2622
- const smtp_cc = this.config.scimgateway.emailOnError.smtp.cc
2623
- transporter.sendMail(mailOptions, function (err) {
2624
- if (err != null) logger.error(`${gwName}[${pluginName}] mailOnError sending failed: ${err.message}`)
2625
- else logger.debug(`${gwName}[${pluginName}] mailOnError sent to: ${smtp_to}${(smtp_cc) ? ',' + smtp_cc : ''}`)
2626
- })
2627
- return null
2628
- }) // emailOnError
2633
+ }, (this.config.scimgateway.email.emailOnError.sendInterval || 15) * 1000 * 60)
2634
+ const msgHtml = `<html><body>
2635
+ <p>${msg}</p>
2636
+ <br>
2637
+ <p><strong>This is an automatically generated email - please do NOT reply to this email or forward to others</strong></p>
2638
+ </body></html>`
2639
+
2640
+ const msgObj = {
2641
+ from: this.config.scimgateway.email.emailOnError.from,
2642
+ to: this.config.scimgateway.email.emailOnError.to,
2643
+ cc: this.config.scimgateway.email.emailOnError.cc,
2644
+ subject: this.config.scimgateway.email.emailOnError.subject ? this.config.scimgateway.email.emailOnError.subject : 'SCIM Gateway error message',
2645
+ content: msgHtml,
2646
+ }
2647
+ this.sendMail(msgObj, true)
2648
+ })
2629
2649
 
2630
2650
  const gracefulShutdown = async function () {
2631
2651
  if (server) {
@@ -2642,6 +2662,7 @@ export class ScimGateway {
2642
2662
  if (typeof Bun !== 'undefined') {
2643
2663
  await Bun.sleep(400) // give in-flight requests a chance to complete, also plugins may use SIGTERM/SIGINT
2644
2664
  server.stop()
2665
+ process.exit(0)
2645
2666
  } else {
2646
2667
  server.close(function () {
2647
2668
  setTimeout(function () { // plugins may also use SIGTERM/SIGINT
@@ -2800,6 +2821,205 @@ export class ScimGateway {
2800
2821
  */
2801
2822
  endpointMapper = utilsScim.endpointMapper
2802
2823
 
2824
+ /**
2825
+ * sendMail sends a mail using scimgateway.email configuraration
2826
+ * @param msgObj mail object
2827
+ * @param isHtml set to true if msgObj.content is HTML encoded, else false for plain text
2828
+ * @remarks
2829
+ * msgObj example:
2830
+ * ```
2831
+ * {
2832
+ * from: 'firstname.lastname@company.com',
2833
+ * to: 'servicedesk@company.com',
2834
+ * cc: 'operators@company.com',
2835
+ * subject: 'SCIM Gateway message',
2836
+ * content: '<html><body><p>Testing <b>HTML encoded</b> message</p></body></html>',
2837
+ * }
2838
+ * ```
2839
+ * email server and authentication being used is defiend in configuration file setting scimgateway.email
2840
+ * example below using **SMTP AUTH**
2841
+ * note, msgObj.from should normally correspond with configuration auth.options.username
2842
+ * ```
2843
+ * {
2844
+ * "scimgateway": {
2845
+ * "email": {
2846
+ * "host": "<host>", // smtp.gmail.com
2847
+ * "port": <port>, // 587
2848
+ * "auth": {
2849
+ * "type": "basic",
2850
+ * "options": {
2851
+ * "username": "<email address>",
2852
+ * "password": "<password>" // app password
2853
+ * }
2854
+ * },
2855
+ * "proxy": {
2856
+ * "host": null, // http://proxy-host:1234
2857
+ * "username": null,
2858
+ * "password": null
2859
+ * }
2860
+ * },
2861
+ * ...
2862
+ * }
2863
+ * }
2864
+ * ```
2865
+ * example below using recommended **OAuth**
2866
+ * note, Microsoft do not default support SMTP AUTH anymore and OAuth should be used
2867
+ * ```
2868
+ * {
2869
+ * "scimgateway": {
2870
+ * "email": {
2871
+ * "host": "<host>", // required when not using tenantIdGUID (Microsoft)
2872
+ * "port": <port>, // required when not using tenantIdGUID (Microsoft)
2873
+ * "auth": {
2874
+ * "type": "oauth",
2875
+ * "options": {
2876
+ * "tenantIdGUID": "<tenantId>", // used for Microsoft Exchange Online
2877
+ * "tokenUrl": "<tokenUrl>", // required when not using tenantIdGUID (Microsoft)
2878
+ * "clientId": "<clientId>",
2879
+ * "clientSecret": "<clientSecret>"
2880
+ * }
2881
+ * },
2882
+ * "proxy": {
2883
+ * "host": null, // http://proxy-host:1234
2884
+ * "username": null,
2885
+ * "password": null
2886
+ * }
2887
+ * },
2888
+ * ...
2889
+ * }
2890
+ * }
2891
+ * ```
2892
+ * Some notes when using OAuth and tenantIdGUID - Microsoft Exchange:
2893
+ * Entra ID application must have application permissions "**Mail.Send**"
2894
+ *
2895
+ * For not allowing send email from all mailboxes, ExO **ApplicationAccessPolicy** must be defined through PowerShell.
2896
+ * First create a mail-enabled security-group that only includes users (mailboxes) the app is allowed to send from
2897
+ * Note, "mail enabled security" cannot be created from portal, only from admin or admin.exchange console
2898
+ * ```
2899
+ * ##Connect to Exchange
2900
+ * Install-Module -Name ExchangeOnlineManagement
2901
+ * Connect-ExchangeOnline
2902
+ *
2903
+ * ##Create ApplicationAccessPolicy
2904
+ * New-ApplicationAccessPolicy -AppId $AppClientID -PolicyScopeGroupId $MailEnabledSecurityGrpId -AccessRight RestrictAccess -Description "Restrict app to specific mailboxes"
2905
+ * ```
2906
+ **/
2907
+ async sendMail(msgObj: Record<string, any>, isHtml: boolean = false) {
2908
+ const gwName = this.gwName
2909
+ const pluginName = this.pluginName
2910
+ const logger = this.logger
2911
+ const authType = this.config.scimgateway?.email?.auth?.type ? this.config.scimgateway.email.auth.type.toLowerCase() : ''
2912
+
2913
+ if (typeof msgObj !== 'object' || !msgObj.from || !msgObj.to || !msgObj.content) {
2914
+ logger.error(`${gwName}[${pluginName}] sendMail failed: missing or invalid msgObj argument`)
2915
+ return
2916
+ }
2917
+
2918
+ if (authType === 'oauth') {
2919
+ if (!this.helperRest) this.helperRest = new HelperRest(this, { entity: { undefined: { connection: this.config.scimgateway.email } } })
2920
+ if (this.config.scimgateway.email.auth?.options?.tenantIdGUID) {
2921
+ // Graph API
2922
+ const emailMessage: Record<string, any> = {
2923
+ message: {
2924
+ subject: msgObj.subject ? msgObj.subject : 'SCIM Gateway message',
2925
+ body: {
2926
+ content: msgObj.content,
2927
+ contentType: isHtml ? 'HTML' : 'Text',
2928
+ },
2929
+ toRecipients: [],
2930
+ ccRecipients: [],
2931
+ },
2932
+ saveToSentItems: 'false',
2933
+ }
2934
+
2935
+ if (msgObj.to) {
2936
+ let arr = msgObj.to.split(',')
2937
+ for (let i = 0; i < arr.length; i++) {
2938
+ emailMessage.message.toRecipients.push({
2939
+ emailAddress: {
2940
+ address: arr[i],
2941
+ },
2942
+ })
2943
+ }
2944
+ }
2945
+ if (msgObj.cc) {
2946
+ const arr = msgObj.cc.split(',')
2947
+ for (let i = 0; i < arr.length; i++) {
2948
+ emailMessage.message.ccRecipients.push({
2949
+ emailAddress: {
2950
+ address: arr[i],
2951
+ },
2952
+ })
2953
+ }
2954
+ }
2955
+ if (emailMessage.message.toRecipients.length === 0) delete emailMessage.message.toRecipients
2956
+ if (emailMessage.message.ccRecipients.length === 0) delete emailMessage.message.ccRecipients
2957
+
2958
+ const path = `/users/${msgObj.from}/sendMail`
2959
+ try {
2960
+ await this.helperRest.doRequest('undefined', 'POST', path, emailMessage)
2961
+ logger.debug(`${gwName}[${pluginName}] sendMail subject '${emailMessage.message.subject}' sent to: ${msgObj.to}${(msgObj.cc) ? ',' + msgObj.cc : ''}`)
2962
+ } catch (err: any) {
2963
+ logger.error(`${gwName}[${pluginName}] sendMail subject '${emailMessage.message.subject}' sending failed: ${err.message}`)
2964
+ }
2965
+ return
2966
+ }
2967
+ }
2968
+
2969
+ // nodemailer
2970
+ if (!this.config.scimgateway?.email?.host) {
2971
+ logger.error(`${gwName}[${pluginName}] sendMail subject '${msgObj.subject}' sending failed: some missing scimgateway.email configuration`)
2972
+ return
2973
+ }
2974
+ const smtpConfig: { [key: string]: any } = {
2975
+ host: this.config.scimgateway.email.host, // e.g. smtp.office365.com
2976
+ port: this.config.scimgateway.email.port || 587,
2977
+ proxy: this.config.scimgateway.email.proxy || null,
2978
+ secure: (this.config.scimgateway.email.port === 465), // false on 25/587
2979
+ tls: { ciphers: 'TLSv1.2' },
2980
+ }
2981
+ if (authType) {
2982
+ smtpConfig.auth = {}
2983
+ if (authType === 'basic') {
2984
+ smtpConfig.auth.user = this.config.scimgateway.email.auth.options.username
2985
+ smtpConfig.auth.pass = this.config.scimgateway.email.auth.options.password
2986
+ } else if (authType === 'oauth') {
2987
+ smtpConfig.auth.type = 'OAuth2'
2988
+ }
2989
+ }
2990
+
2991
+ const transporter = nodemailer.createTransport(smtpConfig)
2992
+
2993
+ const mailOptions: Record<string, any> = {
2994
+ from: msgObj.from, // sender address
2995
+ to: msgObj.to, // list of receivers - comma separated
2996
+ cc: msgObj.cc,
2997
+ subject: msgObj.subject ? msgObj.subject : 'SCIM Gateway message',
2998
+ }
2999
+
3000
+ if (authType === 'oauth') {
3001
+ mailOptions.auth = {}
3002
+ mailOptions.auth.user = msgObj.from
3003
+ transporter.set('oauth2_provision_cb', async (user, renew, callback) => {
3004
+ const aObj = await this.helperRest.getAccessToken('undefined')
3005
+ const accessToken = aObj ? aObj?.access_token : null
3006
+ if (!accessToken) {
3007
+ return callback(new Error('missing access token'))
3008
+ } else {
3009
+ return callback(null, accessToken)
3010
+ }
3011
+ })
3012
+ }
3013
+
3014
+ if (isHtml) mailOptions.html = msgObj.content
3015
+ else mailOptions.text = msgObj.content
3016
+
3017
+ transporter.sendMail(mailOptions, function (err) {
3018
+ if (err != null) logger.error(`${gwName}[${pluginName}] sendMail subject '${mailOptions.subject}' sending failed: ${err.message}`)
3019
+ else logger.debug(`${gwName}[${pluginName}] sendMail subject '${mailOptions.subject}' sent to: ${msgObj.to}${(msgObj.cc) ? ',' + msgObj.cc : ''}`)
3020
+ })
3021
+ }
3022
+
2803
3023
  // processConfig updates this.config and return found.<auth method>
2804
3024
  // config external process.env/file/text replaced with actual values
2805
3025
  // config encryption/decryption for keys named: 'password', 'secret', 'clientSecret', 'token', 'apikey'
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "scimgateway",
3
- "version": "5.0.4",
3
+ "version": "5.0.6",
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)",