scimgateway 6.2.0 → 6.2.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
@@ -1,3727 +1,1404 @@
1
- # SCIM Gateway
1
+ # SCIM Gateway
2
2
 
3
- [![Build Status](https://app.travis-ci.com/jelhub/scimgateway.svg?branch=master)](https://app.travis-ci.com/github/jelhub/scimgateway) [![npm Version](https://img.shields.io/npm/v/scimgateway.svg?style=flat-square&label=latest)](https://www.npmjs.com/package/scimgateway)[![npm Downloads](https://img.shields.io/npm/dm/scimgateway.svg?style=flat-square)](https://www.npmjs.com/package/scimgateway) [![chat disqus](https://jelhub.github.io/images/chat.svg)](https://elshaug.xyz/docs/scimgateway#disqus_thread) [![GitHub forks](https://img.shields.io/github/forks/jelhub/scimgateway.svg?style=social&label=Fork)](https://github.com/jelhub/scimgateway)
3
+ [![Build Status](https://app.travis-ci.com/jelhub/scimgateway.svg?branch=master)](https://app.travis-ci.com/github/jelhub/scimgateway)
4
+ [![npm Version](https://img.shields.io/npm/v/scimgateway.svg?style=flat-square&label=latest)](https://www.npmjs.com/package/scimgateway)
5
+ [![npm Downloads](https://img.shields.io/npm/dm/scimgateway.svg?style=flat-square)](https://www.npmjs.com/package/scimgateway)
6
+ [![GitHub forks](https://img.shields.io/github/forks/jelhub/scimgateway.svg?style=social&label=Fork)](https://github.com/jelhub/scimgateway)
4
7
 
5
- ---
6
- **Author:** Jarle Elshaug
7
-
8
- **Validated through IdPs:**
8
+ **Author:** [Jarle Elshaug](https://www.elshaug.xyz)
9
9
 
10
- * Symantec/Broadcom Identity Manager
11
- * Microsoft Entra ID
12
- * One Identity Manager
13
- * Okta
14
- * Omada
15
- * SailPoint/IdentityNow
16
- ---
10
+ SCIM Gateway is a user provisioning bridge built with [Bun](https://bun.sh/) and [Node.js](https://nodejs.dev/) using TypeScript. It translates incoming SCIM 1.1/2.0 requests into endpoint-specific protocols — turning any destination into a SCIM-compatible interface without vendor lock-in.
17
11
 
18
- Latest news:
19
-
20
- - New `plugin-generic` replacing previous `plugin-scim`. This new plugin use the endpointMapper for flexible attribute mapping and also supports the new mapper option `valueMap` (e.g., group filtering and mapping).
21
- - Now supports `GET /Roles` and `GET /Entitlements` endpoint requests, with corresponding user management via the standard SCIM `roles` and `entitlements` attributes. The Entra ID plugin uses `entitlements` for Entra ID licenses (read-only) and `roles` for Entra ID Permanent and Eligible roles (full management).
22
- - Bun binary build is now supported, allowing SCIM Gateway to be compiled into a single executable binary for simplified deployment and execution. SCIM Gateway can now run as an ES module (TypeScript) in Node.js.
23
- - Major release **v6.0.0** introduces changes to API method responses (not SCIM-related) and a new method `publicApi()` for handling public path `/pub/api` requests with no authentication required. In addition, the configuration option `bearerJwtAzure.tenantIdGUID` has been replaced by `bearerJwt.azureTenantId`. See the version history for details.
24
- - Support for Entra ID [Federated Identity Credentials](https://learn.microsoft.com/en-us/graph/api/resources/federatedidentitycredentials-overview?view=graph-rest-1.0) has been added through internal JWKS (JSON Web Key Set), allowing SCIM Gateway to access Microsoft Entra–protected resources without the need to manage secrets
25
- - External JWKS (JSON Web Key Set) is now supported by JWT authentication, allowing external applications to access SCIM Gateway without the need to manage secrets
26
- - [Azure Relay](https://learn.microsoft.com/en-us/azure/azure-relay/relay-what-is-it) is now supported for secure and hassle-free outbound-only communication — with just one minute of configuration
27
- - [ETag](https://datatracker.ietf.org/doc/html/rfc7644#section-3.14) is now supported
28
- - [Bulk Operations](https://datatracker.ietf.org/doc/html/rfc7644#section-3.7) is now supported
29
- - Remote real-time log subscription for centralized logging and monitoring. Using browser `https://<host>/logger`, curl or custom client API - see configuration notes
30
- - By configuring the chainingBaseUrl, it is now possible to chain multiple gateways in sequence, such as `gateway1->gateway2->gateway3->endpoint`. In this setup, gateway behave like a reverse proxy, validating authorization at each step unless PassThrough mode is enabled. Chaining is also supported in stream subscriber mode
31
- - 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
32
- - Major release **v5.0.0** marks a shift from JavaScript to native TypeScript and prioritizes [Bun](https://bun.sh/) over Node.js. This upgrade requires some modifications to existing plugins.
33
- - **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
34
- - Authentication PassThrough letting plugin pass authentication directly to endpoint for avoid maintaining secrets at the gateway. E.g., using Entra ID application OAuth
35
- - Supports OAuth Client Credentials authentication
36
- - Major release **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
37
- - ipAllowList for restricting access to allowlisted IP addresses or subnets e.g. Azure IP-range
38
- - General LDAP plugin configured for Active Directory
39
- - [PlugSSO](https://elshaug.xyz/docs/plugsso) using SCIM Gateway
40
- - Each authentication configuration allowing more than one admin user including option for readOnly
41
- - Codebase moved from callback of h... to the the promise(d) land of async/await
42
- - Supports configuration by environments and external files
43
- - Health monitoring through "/ping" URL, and option for error notifications by email
44
- - Entra ID user provisioning including license management e.g. Office 365, installed and configured within minutes!
45
- - Includes API Gateway for none SCIM/provisioning - becomes what you want it to become
46
- - Running SCIM Gateway as a Docker container
12
+ ![SCIM Gateway Architecture](https://jelhub.github.io/images/ScimGateway.svg)
47
13
 
48
14
  ---
49
15
 
50
- ## Overview
16
+ ## Table of Contents
17
+
18
+ - [SCIM Gateway](#scim-gateway)
19
+ - [Table of Contents](#table-of-contents)
20
+ - [What's New](#whats-new)
21
+ - [Included Plugins](#included-plugins)
22
+ - [Installation](#installation)
23
+ - [Prerequisites](#prerequisites)
24
+ - [Install SCIM Gateway](#install-scim-gateway)
25
+ - [Verify the Default Loki Plugin](#verify-the-default-loki-plugin)
26
+ - [Upgrading](#upgrading)
27
+ - [Configuration](#configuration)
28
+ - [Entry Point — `index.ts`](#entry-point--indexts)
29
+ - [Plugin File Naming](#plugin-file-naming)
30
+ - [Core Options](#core-options)
31
+ - [Authentication](#authentication)
32
+ - [Basic Authentication](#basic-authentication)
33
+ - [Bearer Token (Shared Secret)](#bearer-token-shared-secret)
34
+ - [JWT (Standard)](#jwt-standard)
35
+ - [OAuth Client Credentials](#oauth-client-credentials)
36
+ - [Authentication PassThrough](#authentication-passthrough)
37
+ - [IP Allow List](#ip-allow-list)
38
+ - [TLS \& Certificates](#tls--certificates)
39
+ - [Using PEM files](#using-pem-files)
40
+ - [Using PFX / PKCS#12](#using-pfx--pkcs12)
41
+ - [No TLS](#no-tls)
42
+ - [Email Notifications](#email-notifications)
43
+ - [Microsoft Exchange Online (OAuth)](#microsoft-exchange-online-oauth)
44
+ - [Google Workspace Gmail (OAuth)](#google-workspace-gmail-oauth)
45
+ - [SMTP Auth](#smtp-auth)
46
+ - [Azure Relay](#azure-relay)
47
+ - [Secrets from External Sources](#secrets-from-external-sources)
48
+ - [Remote Log Subscription](#remote-log-subscription)
49
+ - [Gateway Chaining](#gateway-chaining)
50
+ - [HelperRest](#helperrest)
51
+ - [Basic Auth](#basic-auth)
52
+ - [Entra ID — Client Secret](#entra-id--client-secret)
53
+ - [Entra ID — Certificate Secret](#entra-id--certificate-secret)
54
+ - [Entra ID — Federated Credentials (no secrets)](#entra-id--federated-credentials-no-secrets)
55
+ - [General OAuth (Client Credentials)](#general-oauth-client-credentials)
56
+ - [Single Binary Deployment](#single-binary-deployment)
57
+ - [Running the Gateway](#running-the-gateway)
58
+ - [Manual Startup](#manual-startup)
59
+ - [Windows Task Scheduler](#windows-task-scheduler)
60
+ - [Docker](#docker)
61
+ - [Single Image](#single-image)
62
+ - [Docker Compose](#docker-compose)
63
+ - [Identity Provider Integration](#identity-provider-integration)
64
+ - [Microsoft Entra ID as IdP](#microsoft-entra-id-as-idp)
65
+ - [Symantec/Broadcom Identity Manager as IdP](#symantecbroadcom-identity-manager-as-idp)
66
+ - [Entra ID Provisioning Plugin](#entra-id-provisioning-plugin)
67
+ - [Entra ID App Registration](#entra-id-app-registration)
68
+ - [Plugin Configuration](#plugin-configuration)
69
+ - [Using with Symantec/Broadcom (ConnectorXpress)](#using-with-symantecbroadcom-connectorxpress)
70
+ - [API Gateway](#api-gateway)
71
+ - [Building Custom Plugins](#building-custom-plugins)
72
+ - [Setup](#setup)
73
+ - [Mandatory Plugin Initialization](#mandatory-plugin-initialization)
74
+ - [Implementation Order](#implementation-order)
75
+ - [Plugin Methods](#plugin-methods)
76
+ - [Custom Schemas](#custom-schemas)
77
+ - [License](#license)
78
+ - [Change Log](#change-log)
79
+ - [v6.2.2](#v622)
80
+ - [v6.2.1](#v621)
81
+ - [v6.2.0](#v620)
82
+ - [v6.1.20](#v6120)
83
+ - [v6.1.19](#v6119)
84
+ - [v6.1.18](#v6118)
85
+ - [v6.1.17](#v6117)
86
+ - [v6.1.16](#v6116)
87
+ - [v6.1.15](#v6115)
88
+ - [v6.1.14](#v6114)
89
+ - [v6.1.13](#v6113)
90
+ - [v6.1.12](#v6112)
91
+ - [v6.1.11](#v6111)
92
+ - [v6.1.10](#v6110)
93
+ - [v6.1.9](#v619)
94
+ - [v6.1.8 / v6.1.7](#v618--v617)
95
+ - [v6.1.6](#v616)
96
+ - [v6.1.5](#v615)
97
+ - [v6.1.4](#v614)
98
+ - [v6.1.3](#v613)
99
+ - [v6.1.2](#v612)
100
+ - [v6.1.1](#v611)
101
+ - [v6.1.0](#v610)
102
+ - [v6.0.0 — Major](#v600--major)
103
+ - [v5.x — Previous Major Series](#v5x--previous-major-series)
51
104
 
52
- SCIM Gateway facilitates user management using the standardized REST-based SCIM 1.1 or 2.0 protocol, offering easier, more powerful, and consistent provisioning while avoiding vendor lock-in. Acting as a translator for incoming SCIM requests, the gateway seamlessly enables CRUD functionality (create, read, update, and delete) for users and groups. By implementing endpoint-specific protocols, it ensures provisioning across diverse destinations. With the gateway, your destinations become SCIM-compatible interfaces, streamlining integration and simplifying user management.
105
+ ---
53
106
 
54
- ![](https://jelhub.github.io/images/ScimGateway.svg)
107
+ ## What's New
108
+
109
+ - **`plugin-entra-id`** now supports Entra ID roles and access packages, in addition to reading licenses.
110
+ - **`plugin-generic`** replaces `plugin-scim` — a flexible template using `endpointMapper` with the new `valueMap` option for allowlisting and name mapping e.g., groups
111
+ - **`GET /Roles` and `GET /Entitlements`** endpoint support, with user management via SCIM `roles` and `entitlements` attributes; `plugin-entra-id` uses `entitlements` for Entra ID licenses (read-only) and `roles` for Permanent and Eligible PIM roles (full management)
112
+ - **AI Agent ready** — `x-agent-schema` configuration in `endpointMapper` enables custom schema generation with MCP tool instructions for autonomous provisioning agents
113
+ - **Bun binary builds** — compile a plugin into a single executable for simplified deployment
114
+ - **ES module / TypeScript support in Node.js** via `tsx`
115
+ - **v6.0.0** — API method response bodies returned as-is; new `publicApi()` method for unauthenticated `/pub/api` routes; `bearerJwtAzure.tenantIdGUID` replaced by `bearerJwt.azureTenantId`
116
+ - **Federated Identity Credentials** (Entra ID) — access Microsoft-protected resources without managing secrets, via internal JWKS
117
+ - **External JWKS** support for JWT authentication
118
+ - **Azure Relay** — secure outbound-only tunnel with one minute of setup (~$10/month per listener)
119
+ - **ETag** and **Bulk Operations** support (SCIM RFC 7644)
120
+ - **Remote real-time log subscription** via browser, curl, or custom client at `https://<host>/logger`
121
+ - **Gateway chaining** — chain `gateway1 → gateway2 → gateway3 → endpoint` with reverse-proxy-style auth validation
122
+ - **OAuth for email** — Microsoft Exchange Online and Google Workspace Gmail alongside traditional SMTP Auth
123
+ - [SCIM Stream](https://elshaug.xyz/docs/scim-stream) — subscribe-based provisioning as an alternative to top-down IGA polling
55
124
 
56
- SCIM Gateway is built on the modern, asynchronous, event-driven framework [Bun](https://bun.sh/) or [Node.js](https://nodejs.dev/) using TypeScript/JavaScript. It is designed to be cloud and firewall friendly, runs on nearly all operating systems
125
+ ---
57
126
 
58
- The following fully functional plugins are included for demonstration and production use:
127
+ ## Included Plugins
59
128
 
60
129
  | Plugin | Endpoint Type | Description |
61
- | :--- | :--- | :--- |
62
- | **Loki** | NoSQL Database | Transforms the SCIM Gateway into a standalone SCIM endpoint utilizing the internal [LokiJS](https://github.com/techfort/LokiJS) database. Includes two test users and groups |
63
- | **MongoDB** | NoSQL Database | Similar to the Loki plugin, but using an externally managed MongoDB database, showcasing multi-tenant and multi-endpoint capabilities via `baseEntity` |
64
- | **Entra ID** | REST Webservices | Entra ID user provisioning via Microsoft Graph API |
65
- | **Generic** | REST Webservice | Generic template plugin configured to use plugin-loki as a SCIM provisioning endpoint. Supports the endpointMapper `valueMap` option for allowlisting and mapping (e.g., groups). Can also be used as a SCIM version gateway (e.g., 1.1 => 2.0) |
66
- | **API** | REST Webservices | A non-SCIM plugin demonstrating API Gateway functionality for custom REST specifications |
67
- | **Soap** | SOAP Webservice | Demonstrates user provisioning to a SOAP-based endpoint with example WSDLs |
68
- | **MSSQL** | Database | Demonstrates user provisioning to an MSSQL database |
69
- | **SAP HANA** | Database | Demonstrates SAP HANA-specific user provisioning |
70
- | **LDAP** | Directory | A fully functional LDAP plugin pre-configured for Microsoft Active Directory |
71
-
72
- ## Installation
73
- To get started with SCIM Gateway, follow the instructions below.
74
-
75
- #### Install Bun
76
-
77
- [Bun](https://bun.sh/) is a prerequisite and must be installed
78
-
79
- Note, Bun installs by default in the current user’s `HOMEPATH\.bun`. To install it elsewhere, set `BUN_INSTALL=<install-path>` as a global or system environment variable before installing. The installation will add Bun to the current user’s path, but consider adding it to the global or system path for easier access across all users.
80
-
81
- #### SCIM Gateway Installation
82
-
83
- Create a package directory and install the SCIM Gateway:
84
-
85
- mkdir c:\my-scimgateway
86
- cd c:\my-scimgateway
87
- bun init -y
88
- bun install scimgateway
89
- bun pm trust scimgateway
90
-
91
- index.ts, lib and config directories containing example plugins are copied to your package. The command `bun pm trust scimgateway` is required to allow the `postinstall` script to copy these files.
92
-
93
- #### Startup and verify default Loki plugin
94
-
95
- bun c:\my-scimgateway
96
-
97
- Start a browser
98
-
99
- http://localhost:8880/ping
100
- => Returns a health check with a "hello" response
101
-
102
- http://localhost:8880/Users
103
- http://localhost:8880/Groups
104
- => Logon using gwadmin/password and two users and groups should be listed
105
-
106
- Start a new browser for remote log monitoring
107
- using url: http://localhost:8880/logger
108
-
109
- http://localhost:8880/Users/bjensen
110
- http://localhost:8880/Groups/Admins
111
- or
112
- http://localhost:8880/Users?filter=userName eq "bjensen"
113
- http://localhost:8880/Groups?filter=displayName eq "Admins"
114
- => Lists all attributes for specified user/group
115
-
116
- http://localhost:8880/Groups?filter=displayName eq "Admins"&excludedAttributes=members
117
- http://localhost:8880/Groups?filter=members.value eq "bjensen"&attributes=id,displayName,members.value
118
- http://localhost:8880/Users?filter=userName eq "bjensen"&attributes=userName,id,name.givenName
119
- http://localhost:8880/Users?filter=meta.created ge "2010-01-01T00:00:00Z"&attributes=userName,name.familyName,meta.created
120
- http://localhost:8880/Users?filter=emails.value co "@example.com"&attributes=userName,name.familyName,emails&sortBy=name.familyName&sortOrder=descending
121
- => Filtering and attribute examples
122
-
123
- "Ctrl + c" to stop the SCIM Gateway
124
-
125
- > For Node.js, the startup command is:
126
- `node --import=tsx ./index.ts`
127
-
128
- #### Upgrade Process
129
-
130
- The recommended upgrade method is to rename the existing package folder, perform a fresh installation, and then copy your custom `index.ts`, `config`, and `lib` folders from the previous installation.
131
-
132
- - Minor Upgrade: `bun install scimgateway`
133
- - Major Upgrade: `bun install scimgateway@latest` (Use with caution, as it may break compatibility with existing custom plugins)
134
-
135
- ##### Avoid (re-)adding the example plugins created during `postinstall`
136
-
137
- For production we do not need example plugins to be incuded by the `postinstall` job
138
- Bun will by default exlude any `postinstall` jobs unless we have trusted the scimgateway package using the `bun pm trust scimgateway` that updates package.json `{ trustedDependencies: ["scimgateway"] }`
139
-
140
- For Node.js (and also Bun), we might set the property `scimgateway_postinstall_skip = true` in `.npmrc` or setting environment `SCIMGATEWAY_POSTINSTALL_SKIP = true`
141
-
142
- ## Configuration
143
-
144
- The `index.ts` file spesifies one or more plugins to be started.
145
-
146
- // start one or more plugins:
147
- import './lib/plugin-entra-id.ts'
148
- export {}
149
-
150
-
151
- Each endpoint plugin needs a TypeScript file (.ts) and a configuration file (.json).
152
- They both must have the **same naming prefix**. For the Entra ID endpoint, the corresponding files are:
153
- >lib\plugin-entra-id.ts
154
- >config\plugin-entra-id.json
155
-
156
- A plugin configuration file has two main JSON objects: `scimgateway` and `endpoint`
157
-
158
- {
159
- "scimgateway": {
160
- ...
161
- },
162
- "endpoint": {
163
- ...
164
- }
165
- }
166
-
167
- `scimgateway`: Contains fixed attributes used by the core gateway functionality, such as port, logging, and authentication.
168
-
169
- `endpoint`: Contains customized definitions required by the plugin code for communication with the destination system, including host, port, and credentials.
170
-
171
- - **port**: The gateway will listen on this port number. Clients, such as a provisioning server, will use this port to communicate with the gateway.
172
-
173
- - **localhostonly**: Set to `true` to accept incoming requests only from localhost (127.0.0.1). Set to `false` to accept requests from all clients.
174
-
175
- - **chainingBaseUrl**: The base URL for chaining another gateway, with the syntax `http(s)://host:port`. When defined, the gateway behaves like a reverse proxy, validating authorization unless PassThrough mode is enabled. See `Configuration notes` for details.
176
-
177
- - **idleTimeout**: The number of seconds to wait before timing out a connection due to inactivity. The default value is 120 seconds.
178
-
179
- - **scim.version**: Specifies the SCIM protocol version to use, either "1.1" or "2.0". The default is "2.0".
180
-
181
- - **scim.skipTypeConvert**: When set to `true`, multivalue attributes with types (e.g., emails, phoneNumbers, ims, photos, addresses, entitlements, and x509Certificates, but not roles, groups, and members) will not be converted into "type converted objects" when sent to `modifyUser` and `createUser`. This is useful for simplifying attribute checks and for the `endpointMapper` method used by `plugin-ldap` and `plugin-entra-id`. For example:
182
-
183
- "emails": {
184
- "work": {"value": "jsmith@example.com", "type": "work"},
185
- "home": {"value": "", "type": "home", "operation": "delete"},
186
- "undefined": {"value": "jsmith@hotmail.com"}
187
- }
188
-
189
- When `skipTypeConvert` is set to `true`, the attribute is provided "as-is" as an array, allowing duplicate types including blank types. Values to be deleted are marked with `"operation": "delete"`.
190
-
191
- "emails": [
192
- {"value": "jsmith@example.com", "type": "work"},
193
- {"value": "john.smith.org", "type": "home", "operation": "delete"},
194
- {"value": "jsmith@hotmail.com"}
195
- ]
196
-
197
- - **scim.skipMetaLocation**: When set to `true`, the `meta.location` attribute, which contains the protocol and hostname from the request URL, will be excluded from the response (e.g., `"{...,meta":{"location":"https://my-company.com/<...>"}}`). This is useful when using a reverse proxy and not including the `X-Forwarded-Proto` and `X-Forwarded-Host` headers, as the originator will be the proxy and the internal protocol and hostname should not be exposed.
198
-
199
- - **scim.groupMemberOfUser**: When set to `true`, and the request body contains groups, the `groups` attribute will remain on the user object (groups are members of the user). The default behavior is for the user to be a member of the groups, which uses the `modifyGroup` method to maintain group members.
200
-
201
- - **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.
202
-
203
- - **log.loglevel.file** - off, debug, info, warn or error. Default off. Output to plugin-logfile e.g. `logs\plugin-saphana.log`
204
-
205
- - **log.loglevel.console** - off, debug, info, warn or error. Default off. Output to stdout and errors to stderr
206
-
207
- - **log.loglevel.push** - debug, info, warn or error. Default info. Push to stream used by remote real-time log subscription
208
-
209
- - **log.logDirectory** - custom defined log directory e.g. `/var/log/scimgateway` that will override default `<scimgateway path>/logs`. If not exist it will be created.
210
-
211
- - **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.
212
-
213
- - **log.colorize** - default true, gives colorized and minimized console output, if redirected to stdout/stderr standard JSON formatted output and no colors. Set to false give standard JSON
214
-
215
- - **log.maxSize** - default 20 (MB) log file size
216
-
217
- - **log.maxFiles** - default 5, keep only the last 5 logs - note, new and rotated file on startup
218
-
219
- - **auth** - Contains one or more authentication/authorization methods used by clients for accessing gateway - may also include:
220
- - **auth.xx.readOnly** - true/false, true gives read only access - only allowing `GET` requests for corresponding admin user
221
- - **auth.xx.baseEntities** - array containing one or more `baseEntity` allowed for this user e.g. ["client-a"] - empty array allowing all.
222
- **Methods are disabled by setting corresponding admin user to null or remove methods not used**
223
-
224
- - **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.
225
-
226
- - **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.
227
-
228
- - **auth.bearerJwt** - Array of one or more standard JWT objects. Using **secret**, **publicKey**, **wellKnownUri** or **azureTenantId** for signature verification. publicKey should be set to the filename of public key or certificate pem-file located in `<package-root>\config\certs` or absolute path being used. Clear text secret will become encrypted when gateway is started. For JWKS (JSON Web Key Set), the **wellKnownUri** must be set to identity provider well-known URI which will be used for lookup the jwks_uri key. **options.issuer** should normally be set for validation when using secret or publicKey, for JWKS (wellKnownUri), the issuer will be included automatically. Other options may also be included according to the JWT standard. When using Azure Entra ID provisioning through scimgateway, set **azureTenantId** to the Entra tenant id. When using Entra ID application accessing gateway use: `wellKnownUri=https://login.microsoftonline.com/{tenant-id}/v2.0/.well-known/openid-configuration` and `options.audience={application-id}`
229
-
230
- - **auth.bearerOAuth** - Array of one or more Client Credentials OAuth configuration objects. **`clientId`** and **`clientSecret`** are mandatory. clientSecret value will become encrypted when gateway is started. OAuth token request url is **/oauth/token** e.g. `http://localhost:8880/oauth/token`
231
-
232
- - **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
233
-
234
- - **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:
235
-
236
- "certificate": {
237
- "key": "key.pem",
238
- "cert": "cert.pem",
239
- "ca": "ca.pem" // if several: "ca": ["ca1.pem", "ca2.pem"]
240
- }
241
-
242
- Example of how to make a self signed certificate:
243
-
244
- openssl req -nodes -newkey rsa:2048 -x509 -sha256 -days 3650 -keyout key.pem -out cert.pem -subj "/O=My Company/OU=Application/CN=SCIM Gateway" -addext "subjectAltName=DNS:localhost,DNS:127.0.0.1,DNS:*.mycompany.com" -addext "extendedKeyUsage=serverAuth" -addext "keyUsage=digitalSignature"
245
-
246
- Note, when using Symantec/Broadcom Provisioning, the "certificate authority - CA" also must be imported on the Connector Server. For self-signed certificate, CA and the certificate (public key) is the same.
247
-
248
- PFX / PKCS#12 bundle can be used instead of key/cert/ca e.g:
249
-
250
- "pfx": {
251
- "bundle": "certbundle.pfx",
252
- "password": "password"
253
- }
254
-
255
- Note, we should normally use certificate (https) for communicating with SCIM Gateway unless we install gateway locally on the manager (e.g. on the provisioning Connector Server). When installed on the manager, we could use `http://localhost:port` or `http://127.0.0.1:port` which will not be passed down to the data link layer for transmission. We could then also set {"localhostonly": true}
256
-
257
- - **ipAllowList** - Array of one or more IPv4/IPv6 subnets (CIDR) allowed for incoming traffic. E.g. using Entra ID as IdP, we would like to restrict access to IP addresses used by Azure. Azure IP-range can be downloaded from: [https://azureipranges.azurewebsites.net](https://azureipranges.azurewebsites.net), enter **AzureActiveDirectory** in the search list and select JSON download. Copy the "addressPrefixes" array content and paste into ipAllowList array. CIDR single IP-host syntax is a.b.c.d/32. Note, front-end HTTP proxy or a load balancer must include client IP-address in the **X-Forwarded-For** header. Configuration example:
258
-
259
- "ipAllowList": [
260
- "13.64.151.161/32",
261
- "13.66.141.64/27",
262
- ...
263
- "2603:1056:2000::/48",
264
- "2603:1057:2::/48"
265
- ]
266
- - **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
267
- - **email.auth** - Authentication configuration
268
- - **email.auth.type** - `oauth` or `smtp`
269
- - **email.auth.options** - Authentication options - note, different options for type oauth and smtp
270
- - **email.auth.options.azureTenantId (oauth/ExO)** - Entra tenant id or domain name
271
- - **email.auth.options.clientId (oauth/ExO)** - Entra OAuth application Client ID
272
- - **email.auth.options.clientSecret (oauth/ExO)** - Entra OAuth application Client Secret
273
- - **email.auth.options.serviceAccountKeyFile (oauth/Gmail)** - Google Service Account key json-file name located in the `package-root>\config\certs` directory unless absolute path being defined
274
- - **email.auth.options.host (smtp)** - Mailserver e.g. "smtp.gmail.com" - mandatory for smtp
275
- - **email.auth.options.port (smtp)** - Port used by mailserver e.g. 587, 25 or 465 - mandatory for smtp
276
- - **email.auth.options.username (smtp)** - Mail account for authentication normally same as sender of the email, e.g. "user@gmail.com"
277
- - **email.auth.options.password (smtp)** - Mail account password
278
- - **email.proxy** - Proxy configuration if using mailproxy
279
- - **email.proxy.host** - Proxy host e.g. `http://proxy-host:1234`
280
- - **email.proxy.username** - username if authentication is required
281
- - **email.proxy.password** - password if authentication is required
282
- - **email.emailOnError** - Contains configuration for sending error notifications by email. Note, only the first error will be sent until sendInterval have passed
283
- - **email.emailOnError.enabled** - true or false, value set to true will enable email notifications
284
- - **email.emailOnError.sendInterval** - Default 15. Mail notifications on error are deferred until sendInterval **minutes** have passed since the last notification
285
- - **email.emailOnError.from** - Sender email addresses e.g: "noreply@example.com". **Mandatory for oauth**. For smtp email.auth.options.username will be used
286
- - **email.emailOnError.to** - Comma separated list of recipients email addresses e.g: "someone@example.com"
287
- - **email.emailOnError.cc** - Optional comma separated list of cc mail addresses
288
- - **email.emailOnError.subject** - Optional mail subject, default `SCIM Gateway error message`
289
-
290
- - **azureRelay** - Azure Relay outbound listener
291
- - **azureRelay.enabled** - true or false, true will enable the Azure Relay listener
292
- - **azureRelay.connectionUrl** - `https://<namespace-name>.servicebus.windows.net/<hybrid-connection-name>` - `<namespace-name>` is the name of the Relay created and `<hybrid-connection-name>` is the name of the Hybrid Connection entity created in the Relay
293
- - **azureRelay.apiKey** - The `Private Key` found in the `Shared access policy` (RootManageSharedaccessKey)
294
- - **azureRelay.keyRule** - Optional, the `Shared access policy` name - default using `RootManageSharedaccessKey`
295
-
296
- - **stream** - See [SCIM Stream](https://elshaug.xyz/docs/scim-stream) for configuration details
297
-
298
- - **endpoint** - Contains endpoint specific configuration according to customized **plugin code**.
299
-
300
- ### Configuration notes - general
301
-
302
- - 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.
303
- - Using reverse proxy and we want ipAllowList and correct meta.location response, following headers must be set by proxy: `X-Forwarded-For`, `X-Forwarded-Proto` and `X-Forwarded-Host`
304
- - Setting environment variable `SEED` with some random characters will override default password seeding logic. This also allow copying configuration file with encrypted secrets from one machine to another.
305
- - All configuration can be set based on environment variables. Syntax will then be `"process.env.<ENVIRONMENT>"` where `<ENVIRONMENT>` is the environment variable used. E.g. scimgateway.port could have value "process.env.PORT", then using environment variable PORT.
306
- - All configuration values can be moved to a single external file having JSON dot notation content with plugin name as parent JSON object. Syntax in original configuration file used by the gateway will then be `"process.file.<path>"` where `<path>` is the file used. E.g. key endpoint.password could have value "process.file./var/run/vault/secrets.json"
307
- - All configuration values can be moved to multiple external files, each file containing one single value. Syntax in original configuration file used by the gateway will then be `"process.text.<path>"` where `<path>` is the file which contains raw (`UTF-8`) character value. E.g. key endpoint.password could have value "process.text./var/run/vault/endpoint.password".
308
-
309
- Example:
310
-
311
- {
312
- "scimgateway": {
313
- ...
314
- "port": "process.env.PORT",
315
- ...
316
- "loglevel": {
317
- "file": "process.env.LOG_LEVEL_FILE",
318
- ...
319
- "auth": {
320
- "basic": [
321
- {
322
- "username": "process.file./var/run/vault/secrets.json",
323
- "password": "process.file./var/run/vault/secrets.json"
324
- },
325
- ...
326
- ],
327
- "bearerJwt": [
328
- "secret": "process.text./var/run/vault/jwt.secret",
329
- "publicKey": "process.text./var/run/vault/jwt.pub",
330
- ...
331
- ],
332
- ...
333
- },
334
- "endpoint": {
335
- ...
336
- "username": "process.file./var/run/vault/secrets.json",
337
- "password": "process.file./var/run/vault/secrets.json",
338
- ...
339
- }
340
- }
341
-
342
-
343
- jwt.secret file content example:
344
-
345
- thisIsSecret
346
-
347
- secrets.json file content example for plugin-soap:
348
-
349
- {
350
- "plugin-soap.scimgateway.auth.basic[0].username": "gwadmin",
351
- "plugin-soap.scimgateway.auth.basic[0].password": "password",
352
- "plugin-soap.endpoint.username": "superuser",
353
- "plugin-soap.endpoint.password": "secret"
354
- }
355
-
356
- ### Configuration notes - Email, using Microsoft Exchange Online (ExO)
357
-
358
- - Entra ID application must have application permissions `Mail.Send`
359
- - To prevent the sending of emails from any defined mailboxes, an ExO `ApplicationAccessPolicy` must be defined through PowerShell.
360
-
361
- First create a mail-enabled security-group that only includes those users (mailboxes) the application is allowed to send from
362
- Note, `mail enabled security group` cannot be created from portal, only from admin or admin.exchange console
363
-
364
- ##Connect to Exchange
365
- Install-Module -Name ExchangeOnlineManagement
366
- Connect-ExchangeOnline
367
-
368
- ##Create ApplicationAccessPolicy
369
- New-ApplicationAccessPolicy -AppId <AppClientID> -PolicyScopeGroupId <MailEnabledSecurityGrpId> -AccessRight RestrictAccess -Description "Restrict app to specific mailboxes"
370
-
371
- ### Configuration notes - Email, using Google Workspace Gmail
372
-
373
- - https://console.cloud.google.com
374
- - IAM & Admin > Service Accounts > Create Service Account
375
- - Name=email-sender
376
- - Create and Continue
377
- - Grant this service account access to project - not needed
378
- - Grant users access to this service - not needed
379
- - IAM & Admin > Service Accounts > "email-sender" account > Keys
380
- - Add Key > Create new key > JSON
381
- - download json Service Account Key file, refere to configuration `email.auth.options.serviceAccountKeyFile`
382
-
383
- - https://admin.google.com
384
- - Security > Access and data control > API controls
385
- - Manage Domain Wide Delegation > Add new
386
- - Client ID = id of service account created
387
- - OAuth scope = `https://www.googleapis.com/auth/gmail.send`
388
-
389
- - https://admin.google.com
390
- - Billing > Subscriptions - verify Google Workspace license
391
- - Directory > Users > "user"
392
- - Licenses > Edit > enable Google Workspace license
393
- `email.emailOnError.from` mail address must have Google Workspace license
394
-
395
- ### Configuration notes - Gateway chainging and chainingBaseUrl
396
-
397
- By configuring the `chainingBaseUrl`, it is possible to chain multiple gateways in sequence, such as `gateway1->gateway2->gateway3->endpoint`. In this setup, gateway behave much like a reverse proxy, validating authorization at each step unless PassThrough mode is enabled. Chaining is also supported in stream subscriber mode
398
-
399
- {
400
- "scimgateway": {
401
- ...
402
- "chainingBaseUrl": "https:\\gateway2:8880",
403
- ...
404
- "auth": {
405
- ...
406
- "passThrough": {
407
- "enabled": false,
408
- "readOnly": false,
409
- "baseEntities": []
410
- }
411
- ...
412
- }
413
- },
414
- ...
415
- }
416
-
417
-
418
- Using above configuration example on gateway1, incoming requests will be routed to `https:\\gateway2:8880`
419
-
420
- The plugin and its associated authentication configuration can mirror the setup running on the final gateway. However, in chaining mode, the plugin binary is used solely for initializing and configuring the gateway. This allows for the use of a simplified `plugin-<name>.ts` binary containing only the essential mandatory components:
421
-
422
- // start - mandatory plugin initialization
423
- const ScimGateway: typeof import('scimgateway').ScimGateway = await (async () => {
424
- try {
425
- return (await import('scimgateway')).ScimGateway
426
- } catch (err) {
427
- const source = './scimgateway.ts'
428
- return (await import(source)).ScimGateway
429
- }
430
- })()
431
- const scimgateway = new ScimGateway()
432
- const config = scimgateway.getConfig()
433
- scimgateway.authPassThroughAllowed = false
434
- // end - mandatory plugin initialization
435
-
436
- Using `scimgateway.authPassThroughAllowed = true` and `plugin-<name>.json` configuration `scimgateway.auth.passThrough=true` enables Authentication PassTrhough
437
-
438
- ### Configuration notes - HelperRest used by plugins
439
- For REST endpoints, plugins may use HelperRest to simplify authentication and communication
440
- doRequest() executes REST request and return response
441
- `doRequest(<baseEntity>, <method>, <path>, <body>, <ctx>, <options>)`
442
-
443
- * baseEntity - 'undefined' if not used and must correspond with endpoint configuration that defines baseUrls and connection options.
444
- * method - GET, PATCH, PUT, DELETE
445
- * path - either full url or just the path that will be added to baseUrl. Using full url will override baseUrl. Using path is preferred because of auth caching logic and simplicity
446
- * body - optional body to be used
447
- * ctx - optional, passing authorization header if Auth PassThrough is enabled
448
- * opt - optional, connection options that will extend/override any endpoint.entity.undefined.connection definitions
449
-
450
- Configuration showing connection settings:
451
-
452
- {
453
- "scimgateway": {
454
- ...
455
- }
456
- "endpoint": {
457
- "entity": {
458
- "undefined": {
459
- "connection": {
460
- "baseUrls": [],
461
- "auth": {
462
- "type": "xxx",
463
- "options": {
464
- ...
465
- "jwtPayload": {},
466
- "samlPayload": {},
467
- "tls": {} // files located in ./config/certs
468
- }
469
- },
470
- "options": {
471
- "headers": {},
472
- "tls": {} // files located in ./config/certs
473
- },
474
- "proxy": {}
475
- }
476
- }
477
- }
478
- }
479
- }
480
-
481
-
482
- * baseUrls - Endpoint URL. Several may be defined for failower. There are retry logic on connection failures
483
- * auth.type - defines authentication being used: `basic`, `oauth`, `token`, `bearer`, `oauthSamlBearer` or `oauthJwtBearer`
484
- * auth.options - for each valid type there are different options. azureTenantId is special for Entra ID and serviceAccountKeyFile is special for Google. Using these will simplify and reduce options to be included. Also note we do not need to include baseUrls when using azureTenantId/serviceAccountKeyFile as long as endpoint is Entra ID (Microsoft Graph) or Google.
485
-
486
- Example using basic auth:
487
-
488
- "connection": {
489
- "baseUrls": [
490
- "https://localhost:8880"
491
- ],
492
- "auth": {
493
- "type": "basic",
494
- "options": {
495
- "username": "gwadmin",
496
- "password": "password"
497
- }
498
- },
499
- "options": {
500
- "tls": {
501
- "rejectUnauthorized": false,
502
- "ca": "ca.pem"
503
- }
504
- }
505
- }
506
-
507
- Example Entra ID (plugin-entra-id) using clientId/clientSecret:
508
-
509
- "connection": {
510
- "baseUrls": [],
511
- "auth": {
512
- "type": "oauth",
513
- "options": {
514
- "azureTenantId": "<tenantId>",
515
- "clientId": "<clientId>",
516
- "clientSecret": "<clientSecret>"
517
- }
518
- }
519
- }
520
-
521
- Example Entra ID (plugin-entra-id) using certificate secret:
522
-
523
- "connection": {
524
- "baseUrls": [],
525
- "auth": {
526
- "type": "oauthJwtBearer",
527
- "options": {
528
- "azureTenantId": "<tenantId>",
529
- "clientId": "<clientId>",
530
- "tls": {
531
- "key": "key.pem",
532
- "cert": "cert.pem"
533
- }
534
- }
535
- }
536
- }
537
-
538
- Example Entra ID (plugin-entra-id) using federated credentials:
539
-
540
- "connection": {
541
- "baseUrls": [],
542
- "auth": {
543
- "type": "oauthJwtBearer",
544
- "options": {
545
- "azureTenantId": "<tenantId>",
546
- "fedCred": {
547
- "issuer": "<https://FQDN-scimgateway>",
548
- "subject": "<entra id application object id - client id>",
549
- "name": "<entra id federated credentials unique name>"
550
- }
551
- }
552
- }
553
- }
554
- // Note, fedCred configuration must match corresponding configuration in Entra ID Application - Certificates & Secrets - Federated credentials - scenario "Other issuer"
555
- // example issuer: "https://scimgateway.my-company.com" note, this scimgateway base URL must be reachable from the internet
556
- // example name: "plugin-entra-id"
557
-
558
-
559
- Example using general OAuth:
560
-
561
- "connection": {
562
- "baseUrls": [<"endpointUrl">],
563
- "auth": {
564
- "type": "oauth",
565
- "options": {
566
- "tokenUrl": "<tokenUrl>"
567
- "clientId": "<clientId>",
568
- "clientSecret": "<clientSecret>"
569
- }
570
- }
571
- }
572
-
573
- Please see code editor method HelperRest doRequest() IntelliSense for type and option details
574
-
575
- ### Configuration notes - Remote real-time log subscription
576
- Using remote real-time log subscription we may implement custom logic like monitoring and centralized logging
577
-
578
- - browser and url: https://host/logger
579
- - curl with -u or -H "Authorization: Bearer secret"
580
- ```
581
- curl -Ns http://localhost:8880/logger -u gwadmin:password | awk '
582
- /^data: / {sub(/^data: /,""); printf "%s", $0; last=1; next}
583
- /^$/ {if (last) print ""; last=0}
584
- '
585
- ```
586
- - custom client API (see example below)
587
- - not supported by Azure Relay
588
-
589
-
590
- We may configure read-only user/secret for log collection purpose
591
-
592
- "auth": {
593
- "basic": [
594
- {
595
- "username": "gwadmin",
596
- "password": "password",
597
- "readOnly": false,
598
- "baseEntities": []
599
- },
600
- {
601
- "username": "gwread",
602
- "password": "password",
603
- "readOnly": true,
604
- "baseEntities": []
605
- }
606
- ],
607
- "bearerToken": [
608
- {
609
- "token": "secret",
610
- "readOnly": true,
611
- "baseEntities": []
612
- }
613
- ],
614
- ...
615
- }
616
-
617
- Remote log subscription is configured by log.loglevel.push and the push logger has default loglevel set to `info`
618
- Example using debug loglevel:
619
-
620
- "log": {
621
- "loglevel": {
622
- "push": "debug"
623
- }
624
- }
625
-
626
- Example code implementing remote real-time log subscription and custom message handling
627
-
628
- ```
629
- //
630
- // usage: bun <scriptname.ts>
631
- // update url and the auth according to environment used
632
- //
633
- const username = "gwadmin"
634
- const password = "password"
635
- const url = "http://localhost:8880/logger"
636
-
637
- const headers = new Headers({
638
- Authorization: "Basic " + btoa(`${username}:${password}`),
639
- Accept: "text/event-stream"
640
- })
641
-
642
- // message handling and custom logic
643
- // we could also do JSON.parse(message) and granular filtering on log "level"
644
- const messageHandler = async (message: string) => {
645
- console.log(message)
646
- }
647
-
648
- async function startup() {
649
- while (true) {
650
- try {
651
- const resp = await fetch(url, { headers });
652
- if (!resp.ok || !resp.body) {
653
- console.error(`❌ Response error: ${resp.status} ${resp.statusText}`)
654
- await Bun.sleep(10_000)
655
- continue
656
- }
657
- console.log('✅ Now awaiting log events...\n')
658
-
659
- const reader = resp.body.pipeThrough(new TextDecoderStream()).getReader()
660
-
661
- while (true) {
662
- const { value, done } = await reader.read()
663
- if (done) break
664
- if (!value.startsWith('data: ')) continue
665
- const i = value.indexOf("\n\n")
666
- if (i < 1) continue
667
- const msg = value.slice(6, i)
668
- messageHandler(msg)
669
- }
670
- console.error("⚠️ Connection closed");
671
- await Bun.sleep(10_000)
672
- } catch (err: any) {
673
- console.error("❌ Connection error:", err?.message || err)
674
- await Bun.sleep(10_000)
675
- }
676
- }
677
- }
678
-
679
- startup()
680
- ```
681
-
682
- ### Configuration notes - Azure Relay
683
-
684
- Using Azure technology we have different options for setting up a communication tunnel to SCIM Gateway:
685
-
686
- - `Microsoft Entra Application Proxy + Microsoft Entra Application Proxy Connector` (SCIM Gateway located on-premises or using Azure private VNet/IP)
687
- - `Azure Application Gateway` - Layer 7 (SCIM Gateway located in Azure)
688
- - `Azure Relay` (SCIM Gateway located on-premises or in Azure)
689
-
690
- SCIM Gateway have builtin [Azure Relay](https://learn.microsoft.com/en-us/azure/azure-relay/relay-what-is-it) support which gives secure and hassle-free outbound communication — with just one minute of configuration
691
-
692
- Azure pricing for using Azure Relay is approx. 10$ per month for each listener (SCIM Gateway plugin)
693
-
694
- **Using out-of-the-box Azure Relay:**
695
-
696
- - Prerequisite: SCIM Gateway having outbound internet access (https/443)
697
- - In Azure create a `Relay` - `<namespace-name>`
698
- - In the Relay, create an entity of type `Hybrid Connection` - `<hybrid-connection-name>` **one for each SCIM Gateway plugin**
699
- - The `Requires Client Authorization` option **should be unchecked (not activated)**, unless we are using custom IdP/API having logic for including SAS-token in the communication header
700
- - Shared access policies - RootManageSharedaccessKey - Primary Key (copy this one)
701
- Instead of RootManageSharedaccessKey policy in the `<namespace-name>`, we could create dedicated policy in the sub level `<hybrid-connection-name>` and use this policy name in plugin configuration `scimgateway.azureRelay.keyRule`
702
-
703
- SCIM Gateway plugin configuration:
704
-
705
- ```
706
- {
707
- "scimgateway: {
708
- ...
709
- "azureRelay": {
710
- "enabled": true,
711
- "connectionUrl": "https://<namespace-name>.servicebus.windows.net/<hybrid-connection-name>",
712
- "apiKey": "<primary-key>"
713
- },
714
- ...
715
- },
716
- ...
717
- }
718
- ````
719
-
720
- `connectionUrl` will be the SCIM base URL used by IdP/API for accessing SCIM Gateway
721
-
722
- Example:
723
- GET `https://<namespace-name>.servicebus.windows.net/<hybrid-connection-name>/Users`
724
- GET `https://<namespace-name>.servicebus.windows.net/<hybrid-connection-name>/<baseEntity>/Users`
725
-
726
- If several SCIM Gateway´s (same plugin) connect listeners using the same Azure Relay connectionUrl, there will be load-balancing and round-robin distribution
727
-
728
- ### Configuration notes - running SCIM Gateway as a single binary
729
-
730
- Bun binary build allowing SCIM Gateway to be compiled into a single executable binary for simplified deployment and execution. The binary must have the same name (prefix) as the configuration file in the config directory, and this directory must be located in the same folder as the binary.
130
+ |---|---|---|
131
+ | **loki** | NoSQL | Standalone SCIM endpoint using [LokiJS](https://github.com/techfort/LokiJS). Includes test users and groups. Ideal for development. |
132
+ | **mongodb** | NoSQL | Like Loki but backed by an external MongoDB. Demonstrates multi-tenant via `baseEntity`. |
133
+ | **entra-id** | REST | Users/Groups/Roles/AccessPackages/Licenses provisioning to Microsoft Entra ID via Microsoft Graph API. |
134
+ | **generic** | REST | Generic template using `endpointMapper` and the `valueMap` option for allowlisting and name mapping e.g., groups. Defaults to plugin-loki as the SCIM target. Can also act as a SCIM version gateway (e.g. 1.1 2.0). |
135
+ | **api** | REST | Non-SCIM plugin demonstrating API Gateway mode for custom REST specifications. |
136
+ | **soap** | SOAP | User provisioning to a SOAP-based endpoint with example WSDLs. |
137
+ | **mssql** | SQL | User provisioning to Microsoft SQL Server. |
138
+ | **saphana** | SQL | SAP HANAspecific user provisioning. |
139
+ | **ldap** | Directory | Full LDAP plugin pre-configured for Microsoft Active Directory. |
731
140
 
732
- cd my-scimgateway
733
- bun build --compile ./lib/plugin-loki.ts --target=bun-darwin-arm64 --outfile ./build/plugin-loki
734
- # for target options, see: https://bun.com/docs/bundler/executables#cross-compile-to-other-platforms
735
-
736
- cp -r ./config ./build
737
- # build directory now ready for production deployment
738
- cd build
739
- # run the binary - note, binary must have same name (prefix) as the configuration file in the config directory
740
- ./plugin-loki
741
-
742
-
743
-
744
- ## Manual startup
745
-
746
- Gateway can be started from a command window running in administrative mode
747
-
748
- 3 ways to start:
749
-
750
- bun c:\my-scimgateway
751
-
752
- bun c:\my-scimgateway\index.ts
753
-
754
- <package-root>bun .
755
-
756
-
757
- <kbd>Ctrl</kbd>+<kbd>c</kbd> to stop
758
-
759
- ## Automatic startup - Windows Task Scheduler
760
-
761
- Start Windows Task Scheduler (taskschd.msc), right click on "Task Scheduler Library" and choose "Create Task"
762
-
763
- General tab:
764
- -----------
765
- Name = SCIM Gateway
766
- User account = SYSTEM
767
- Run with highest privileges
768
-
769
- Triggers tab:
770
- -------------
771
- Begin the task = At startup
772
-
773
- Actions tab:
774
- ------------
775
- Action = Start a program
776
- Program/script = <install path>\bun.exe
777
- Arguments = c:\my-scimgateway
778
-
779
- Settings - tab:
780
- ---------------
781
- Stop the task if runs longer than = Disabled (greyed out)
782
-
783
- Verification:
784
-
785
- - Right click task - **Run**, verify process node.exe (SCIM Gateway) can be found in the task manager (not the same as task scheduler). Also verify logfiles `<pakage-root>\logs`
786
- - Right click task - **End**, verify process node.exe have been terminated and disappeared from task manager
787
- - **Reboot** server and verify SCIM Gateway have been automatically started
788
-
789
- ## Running as a isolated virtual Docker container
790
-
791
- Installing Docker Desktop may be an alternative for creating and testing docker images and containers
792
-
793
- There are two options: run SCIM Gateway in a single image, or use Docker Compose, which allows configuration and data outside the image and including other images as dependencies (e.g., MSSQL)
794
-
795
- ### Docker single image
796
-
797
- - Install SCIM Gateway within your own package and copy provided docker files:
798
-
799
- ```
800
- mkdir /opt/my-scimgateway
801
- cd /opt/my-scimgateway
802
- bun init -y
803
- bun install scimgateway
804
- bun pm trust scimgateway
805
- cp ./config/docker/* .
806
- cp ./config/docker/.dockerignore .
807
- ```
808
-
809
- **Dockerfile** <== Main dockerfile
810
- **.dockerignore** <== Files to exclude from the build context
811
-
812
-
813
- - Build docker images
814
-
815
- `docker build --platform linux/amd64 --force-rm=true -t my-scimgateway:1.0.0 .`
816
-
817
- - Create container
818
-
819
- `docker create --init --ulimit memlock=-1:-1 --name my-scimgateway -p 8880:8880 my-scimgateway:1.0.0`
820
-
821
- Note, consider using `-e SEED=<random-characters>` and plugin configuration file my-scimgateway.json must already be encrypted using same SEED environment
822
-
823
- - Start container
824
-
825
- `docker start my-scimgateway`
826
-
827
- - Stop container
828
-
829
- `docker stop my-scimgateway`
830
-
831
- ### Docker image using docker-compose
832
-
833
- * Docker Pre-requisites:
834
- **docker-ce
835
- docker-compose**
836
-
837
- - Install SCIM Gateway within your own package and copy provided docker files:
838
-
839
- ```
840
- mkdir /opt/my-scimgateway
841
- cd /opt/my-scimgateway
842
- bun init -y
843
- bun install scimgateway
844
- bun pm trust scimgateway
845
- cp ./config/docker/* .
846
- ```
847
-
848
- **docker-compose.yml** <== Here is where you would set the exposed port and environment
849
- **Dockerfile** <== Main dockerfile
850
- **DataDockerfile** <== Handles volume mapping
851
- **docker-compose-debug.yml** <== Debugging
852
- **docker-compose-mssql.yml** <== Example including MSSQL docker image
853
- **.dockerignore** <== Files to exclude from the build context
854
-
855
- - Create a scimgateway user on your Linux VM.
856
-
857
- `adduser scimgateway`
858
-
859
- - Create a directory on your VM host for the scimgateway configs:
860
-
861
- `mkdir /home/scimgateway/config`
862
-
863
- - Copy your updated configuration file e.g. /opt/my-scimgateway/config/plugin-loki.json to /home/scimgateway/config. Use scp to perform the copy.
864
-
865
- NOTE: /home/scimgateway/config is where all important configuration and loki datastore will reside outside of the running docker container. If you upgrade scimgateway you won't lose your configurations and data.
866
-
867
- - Build docker images and start it up
868
-
869
- `docker-compose up --build -d`
870
-
871
- NOTE: Add the -d flag to run the command above detached.
872
-
873
- Be sure to confirm that port 8880 is available with a simple http request
874
-
875
- If using default plugin-loki and we have configured `{"persistence": true}`, we could confirm scimgateway created loki.db:
876
-
877
- ```
878
- su scimgateway
879
- cd /home/scimgateway/config
880
- ls loki.db
881
- ```
882
-
883
- To list running containers information:
884
- `docker ps`
885
-
886
- To list available images:
887
- `docker images`
888
-
889
- To view the logs:
890
- `docker logs scimgateway`
891
-
892
- To execute command within your running container:
893
- `docker exec scimgateway <bash command>`
894
-
895
- To stop scimgateway:
896
- `docker-compose stop`
897
-
898
- To restart scimgateway:
899
- `docker-compose start`
900
-
901
- To debug running container (using Visual Studio Code):
902
- `docker-compose -f docker-compose.yml -f docker-compose-debug.yml up -d`
903
- Start Visual Studio Code and follow [these](https://code.visualstudio.com/docs/nodejs/nodejs-debugging) debugging instructions
904
-
905
- To upgrade scimgateway docker image (remove the old stuff before running docker-compose up --build):
906
-
907
- docker rm scimgateway
908
- docker rm $(docker ps -a -q); docker rmi $(docker images -q -f "dangling=true")
909
-
910
- ## Entra ID as IdP using SCIM Gateway
911
-
912
- Entra ID could do automatic user provisioning by synchronizing users towards SCIM Gateway, and gateway plugins will update endpoints.
913
-
914
- Plugin configuration file must include **SCIM Version "2.0"** (scimgateway.scim.version) and either **Bearer Token** (scimgateway.auth.bearerToken[x].token) or **Entra ID Tenant ID** (scimgateway.auth.bearerJwt[x].azureTenantId) or both:
915
-
916
- scimgateway: {
917
- "scim": {
918
- "version": "2.0",
919
- ...
920
- },
921
- ...
922
- "auth": {
923
- "bearerToken": [
924
- {
925
- "token": "shared-secret"
926
- }
927
- ],
928
- "bearerJwt": [
929
- {
930
- "azureTenantId": "xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx"
931
- }
932
- ]
933
- }
934
- ...
935
- }
936
-
937
- `token` configuration must correspond with "Secret Token" defined in Entra ID
938
- `azureTenantId` configuration must correspond with Entra ID Tenant ID
939
-
940
- In Azure Portal:
941
- `Azure-Microsoft Entra ID-Enterprise Application-<My Application>-Provisioning-Secret Token`
942
- Note, when "Secret Token" is left blank, Azure will use JWT (azureTenantId)
943
-
944
- `Azure-Microsoft Entra ID-Overview-Tenant ID`
945
-
946
- User mappings attributes between AD and SCIM also needs to be configured
947
-
948
- `Azure-Microsoft Entra ID-Enterprise Application-<My Application>-Provisioning-Edit attribute mappings-Mappings`
949
-
950
- Entra ID default SCIM attribute mapping for **USER** must have:
951
-
952
- userPrincipalName mapped to userName (matching precedence #1)
953
-
954
-
955
- Entra ID default SCIM attribute mapping for **GROUP** must have:
956
-
957
- displayName mapped to displayName (matching precedence #1)
958
- members mapped to members
959
-
960
-
961
-
962
- Some notes related to Entra ID:
963
-
964
- - Entra ID SCIM [documentation](https://learn.microsoft.com/en-us/entra/identity/app-provisioning/use-scim-to-provision-users-and-groups)
965
-
966
- - For using OAuth/JWT credentials, Entra ID configuration "Secret Token" (bearer token) should be blank. Plugin configuration must then include bearerJwt.azureTenantId. Click "Test Connection" in Azure to verify
967
-
968
- - Entra ID do a regular check for a "non" existing user/group. This check seems to be a "keep alive" to verify connection.
969
-
970
- - Entra ID first checks if user/group exists, if not exist they will be created (no explore of all users like CA Identity Manager)
971
-
972
- - Deleting a user in Entra ID sends a modify user `{"active":"False"}` which means user should be disabled. This logic is default set in attribute mappings expression rule `Switch([IsSoftDeleted], , "False", "True", "True", "False")`. Standard SCIM "DELETE" method seems not to be used.
973
-
974
-
975
- ## Symantec Identity Manager as IdP using SCIM Gateway
976
-
977
- Using Symantec/Broadcom Identity Manger, plugin configuration must use **SCIM Version "1.1"** (scimgateway.scim.version).
978
-
979
- In the Provisioning Manager we could use `Endpoint type = SCIM (DYN Endpoint)` or create our own custom endpoint type based on this one
980
-
981
- SCIM endpoint configuration example for Loki plugin (plugin-loki)
982
-
983
- Endpoint Name = Loki-8880
984
- User Name = gwadmin
985
- Password = password
986
- SCIM Authentication Method = HTTP Basic Authentication
987
- SCIM Based URL = http://localhost:8880
988
-
989
- or:
990
-
991
- SCIM Based URL = http://localhost:8880/<baseEntity>
992
-
993
- Username, password and port must correspond with plugin configuration file. For "Loki" plugin it will be `config\plugin-loki.json`
994
-
995
- "SCIM Based URL" refer to the FQDN (or localhost) having SCIM Gateway installed. Portnumber must be included. Use HTTPS instead of HTTP if SCIM Gateway configuration includes certificates.
996
-
997
- "baseEntity" is optional. This is a parameter used for multi tenant or multi endpoint solutions. We could create several endpoints having same base url with unique baseEntity. e.g:
998
-
999
- http://localhost:8880/client-a
1000
- http://localhost:8880/client-b
1001
-
1002
- Each baseEntity should then be defined in the plugin configuration file with custom attributes needed. Please see examples in plugin-soap.json
1003
-
1004
- ## Entra ID provisioning
1005
- Using plugin-entra-id we could do user provisioning towards Entra ID
1006
-
1007
- For testing purposes we could get an Azure free account
1008
-
1009
- ### Entra ID configuration
1010
-
1011
- - Logon to [Azure](https://portal.azure.com) as global administrator
1012
- - Microsoft Entra ID - App registrations
1013
- - Click "New registration"
1014
- - Name = SCIM Gateway Inbound
1015
- - Select: Accounts in this organizational directory only
1016
- - Click "Register"
1017
- - Overview:
1018
- - Copy "Application (client) ID"
1019
- - Copy "Directory (tentant) ID"
1020
- - Certificates & secrets:
1021
- - Click "New client secret"
1022
- - Description = SCIM Gateway Inbound secret#1
1023
- - Select an appropriate "Expires"
1024
- - Click "Add"
1025
- - Copy "Value" of the new secret that was created
1026
- - API permissions: - Add a permission - Microsoft Graph - Application permissions
1027
- - Optionally remove any defaults included e.g. User.Read
1028
- - Click "Add a permission"
1029
- - Microsoft Graph
1030
- - Application permissions
1031
- - Directory - Directory.ReadWriteAll
1032
- - Organization - Organization.ReadWrite.All
1033
- - AuditLog - AuditLog.Read.All (required if using plugin configuration `map.user.signInActivity`)
1034
- - RoleEligibilitySchedule - RoleEligibilitySchedule.Read.Directory (PIM Eligible roles; required if using plugin configuration `map.user.roles`)
1035
- - RoleManagement - RoleManagement.ReadWrite.Directory' (PIM Permanent roles; required if using plugin configuration `map.user.roles`)
1036
- - Click "Add permissions"
1037
- - API permissions: - Grant Admin consent
1038
- Or we could go to Enterprise application to grant these consents:
1039
- - Microsoft Entra ID - Enterprise applications - SCIM Gateway Inbound
1040
- - Permissions:
1041
- - Click "Grant admin consent for [tenant name]"
1042
- - In the logon dialog, logon as global administrator
1043
- - In permissions request dialog, click "Accept"
1044
- - Click "Refresh", directory and organization permissions are now listed and OK
1045
-
1046
- - Microsoft Entra ID - Manage - Roles and administrators
1047
- - Search: User administrator
1048
- - Click on role **User administrator**
1049
- - Click "Add assignments"
1050
- - Click "No member selected" to add members
1051
- - Search: SCIM Gateway Inbound (name of the application we have created)
1052
- - Select the application name that shows up and click "Add"
1053
- - Click Next
1054
- - Assignment type=Active and enable "Permanent assigned", add some justification text and click "Assign"
1055
-
1056
- Note: Entra ID has a role hierarchy, and running SCIM Gateway as a `User Administrator` has some limitations when administering users who have administrative roles. For full administrative access to all users, SCIM Gateway must have the `Global Administrator` role (`62e90394-69f5-4237-9190-012177145e10`).
1057
-
1058
- ### SCIM Gateway configuration
1059
-
1060
- **Edit index.ts**
1061
- Set plugin to be started to `entra-id`
1062
-
1063
- const plugins = ['entra-id']
1064
-
1065
- **Edit plugin-entra-id.json**
1066
-
1067
- Note, for Symantec/Broadcom Provisioning we must use SCIM version 1.1
1068
-
1069
- scimgateway: {
1070
- "scim": {
1071
- "version": "1.1"
1072
- },
1073
-
1074
- `username` and `password` used to connect the SCIM Gateway must be defined.
1075
-
1076
- "auth": {
1077
- "basic": [
1078
- {
1079
- "username": "gwadmin",
1080
- "password": "password",
1081
- "readOnly": false,
1082
- "baseEntities": []
1083
- }
1084
- ],
1085
-
1086
- Update `azureTenantId`, `clientID` and `clientSecret` according to what you copied from the previous Entra ID configuration.
1087
-
1088
- If using proxy, set proxy.host to `"http://<FQDN-ProxyHost>:<port>"` e.g `"http://proxy.mycompany.com:3128"`
1089
-
1090
- "endpoint": {
1091
- "entity": {
1092
- "undefined": {
1093
- "connection": {
1094
- "baseUrls": [
1095
- "not in use for Entra ID when azureTenantId is defined"
1096
- ],
1097
- "auth": {
1098
- "type": "oauth",
1099
- "options": {
1100
- "tokenUrl": "oauth token_url - not in use when azureTenantId is defined",
1101
- "azureTenantId": "Entra ID Tenant ID (GUID) or Primary domain name - only used by plugin-entra-id",
1102
- "clientId": "oauth client_id - Entra ID: Application ID",
1103
- "clientSecret": "oauth client_secret - Entra ID: generated application secret value"
1104
- }
1105
- },
1106
- "proxy": {
1107
- "host": null,
1108
- "username": null,
1109
- "password": null
1110
- }
1111
- }
1112
- }
1113
- },
1114
- "map": {
1115
- ...
1116
- }
1117
- }
1118
-
1119
- Note, clientSecret and any proxy.password will become encrypted in this file on the first Azure connection.
1120
-
1121
- For multi-tenant or multi-endpoint support, we may add several entities:
1122
-
1123
- "endpoint": {
1124
- "entity": {
1125
- "undefined": {
1126
- ...
1127
- },
1128
- "client-a": {
1129
- ...
1130
- },
1131
- "client-b": {
1132
- ...
1133
- }
1134
- }
1135
- }
1136
-
1137
- For additional details, see baseEntity description.
1138
-
1139
- Note, we should normally use certificate (https) for communicating with SCIM Gateway unless we install gateway locally on the manager (e.g. on the CA Connector Server). When installed on the manager, we could use `http://localhost:port` or `http://127.0.0.1:port` which will not be passed down to the data link layer for transmission. We could then also set {"localhostonly": true}
1140
-
1141
- ### Using Symantec/Broadcom Provisioning
1142
- Create a new endpoint type "Azure - ScimGateway"
1143
-
1144
- - Start SCIM Gateway
1145
- - Using plugin-entra-id: `const plugins = ['entra-id']` in `index.ts`
1146
- - username, password and port defined in `plugin-entra-id.json` must also be known
1147
- - Start ConnectorXpress
1148
- - Setup Data Sources
1149
- - Add
1150
- - Layer7 (this is SCIM)
1151
- - Name = SCIM Gateway-8881
1152
- - Base URL = http://localhost:8881 (SCIM Gateway installed locally on Connector Server)
1153
- - Add the new "Azure - ScimGateway" endpoint type
1154
- - Metadata - Import - "my-scimgateway\node_modules\scimgateway\config\resources\Azure - ScimGateway.xml"
1155
- - Select the datasource we created - SCIM Gateway-8881
1156
- - Enter password for the user defined in datasource (e.g. gwadmin/password)
1157
- - On the right - expand Provisioning Servers - your server - and logon
1158
- - Right Click "Endpoint Types", Create New Endpoint Type
1159
- - You may use default name "Azure - ScimGateway" and click "OK" to create endpoint
1160
-
1161
- Note, metafile "Azure - ScimGateway.xml" is based on CA "Azure - WSL7" with some minor adjustments like using Microsoft Graph API attributes instead of Azure AD Graph attributes.
1162
-
1163
- **Provisioning Manager configuration**
1164
-
1165
- `Endpoint type = Azure - ScimGateway (DYN Endpoint)`
1166
-
1167
- Endpoint configuration example:
1168
-
1169
- Endpoint Name = AzureAD-8881
1170
- User Name = gwadmin
1171
- Password = password
1172
- SCIM Authentication Method = HTTP Basic Authentication
1173
- SCIM Based URL = http://localhost:8881
1174
- or
1175
- SCIM Based URL = http://localhost:8881/<baseEntity>
1176
-
1177
- For details, please see section "CA Identity Manager as IdP using SCIM Gateway"
1178
-
1179
- ## API Gateway
1180
-
1181
- SCIM Gateway also works as an API Gateway when using url `/api` or `/<baseEntity>/api`
1182
-
1183
- Following methods for the none SCIM based api-plugin are supported:
1184
-
1185
- GET /api
1186
- GET /api?queries
1187
- GET /api/{id}
1188
- POST /api + body
1189
- PUT /api/{id} + body
1190
- PATCH /api/{id} + body
1191
- DELETE /api/{id}
1192
-
1193
- These methods can also be included in standard SCIM plugins
1194
- Please see example plugin: **plugin-api.ts**
1195
-
1196
- ## How to build your own plugins
1197
- For coding editor you may use [Visual Studio Code](https://code.visualstudio.com/ "Visual Studio Code")
1198
-
1199
- Preparation:
1200
-
1201
- * 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
1202
- * Edit plugin-mine.json and define a unique port number for the gateway setting
1203
- * Edit index.ts and include your plugin in the startup e.g. `import './lib/plugin-mine.ts'`
1204
- * Start SCIM Gateway and verify
1205
-
1206
- We are now ready for custom coding by editing plugin-mine.ts
1207
- Coding should be done step by step and each step should be verified and tested before starting the next
1208
-
1209
- 1. **Turn off group functionality** - getGroups to return empty response (gateway automatically use getGroups for some of the methods if groups not included)
1210
- Please see plugin-saphana that do not use groups.
1211
- 2. **getUsers** (test provisioning retrieve all accounts and single account)
1212
- 3. **createUser** (test provisioning new account)
1213
- 4. **deleteUser** (test provisioning delete account)
1214
- 5. **modifyUser** (test provisioning modify account)
1215
- 6. **Turn on group functionality** - getGroups having logic for returning groups if groups are supported
1216
- 7. **getGroups** (test provisioning retrieve groups)
1217
- 8. **modifyGroup** (test provisioning modify group members)
1218
- 9. **createGroup** (test provisioning new group)
1219
- 10. **deleteGroup** (test provisioning delete account)
1220
-
1221
- Template used by CA Provisioning role should only include endpoint supported attributes defined in our plugin. Template should therefore have no links to global user for none supported attributes (e.g. remove %UT% from "Job Title" if our endpoint/code do not support title)
1222
-
1223
- CA Provisioning using default SCIM endpoint do not support SCIM Enterprise User Schema Extension (having attributes like employeeNumber, costCenter, organization, division, department and manager). If we need these or other attributes not found in CA Provisioning, we could define our own by using the free-text "type" definition in the multivalue entitlements or roles attribute. In the template entitlements definition, we could for example define type=Company and set value to %UCOMP%. Please see plugin-soap.ts using Company as a multivalue "type" definition.
1224
-
1225
- Using CA Connector Xpress we could create a new SCIM endpoint type based on the original SCIM. We could then add/remove attributes and change from default assign "user to groups" to assign "groups to user". There are also other predefined endpoints based on the original SCIM. You may take a look at "ServiceNow - WSL7" and "Zendesk - WSL7".
1226
-
1227
-
1228
- For project setup:
1229
-
1230
- * Datasource = Layer7 (CA API) - this is SCIM
1231
- * Layer7 Base URL = SCIM Gateway url and port (SCIM Base URL)
1232
- * Authentication = Basic Authentication
1233
- (connect using gwadmin/password defined in plugin config-file)
1234
-
1235
- ### How to change "user member of groups" to "group member of users"
1236
-
1237
- Using Connector Xpress based on the original SCIM endpoint.
1238
-
1239
- Delete defaults:
1240
- Group - Associations - with User Account
1241
- Group - Attributes - members
1242
- User Account - Attributes - Group Membership
1243
-
1244
- Create new attribute:
1245
- User Account - Attributes: Groups - Flexi DN - Multivalue - **groups**
1246
-
1247
- Create User - Group associations:
1248
- User Account - Accociations - **Direct association with = Group**
1249
- User Account - Accociations - with Group
1250
-
1251
- Note, "Include a Reverse Association" - not needed if we don't need Group object functionality e.g list/add/remove group members
1252
-
1253
- User Attribute = **Physical Attribute = Groups**
1254
- Match Group = By Attribute = ID
1255
-
1256
- Objects Must Exist
1257
- Use DNs in Attribute = activated (toggled on)
1258
-
1259
- Include a Reverse Association (if needed)
1260
- Group Attribute = **Virtual Attribute = User Membership**
1261
- Match User Account = By Attribute = User Name
1262
-
1263
- Note, groups should be capability attribute (updated when account is synchronized with template):
1264
- advanced options - **Synchronized** = enabled (toggled on)
1265
-
1266
- ## Methods
1267
-
1268
- Plugins should have following initialization:
141
+ ---
1269
142
 
1270
- // start - mandatory plugin initialization
1271
- import { ScimGateway, HelperRest } from 'scimgateway'
1272
- const scimgateway = new ScimGateway()
1273
- const helper = new HelperRest(scimgateway)
1274
- const config = scimgateway.getConfig()
1275
- scimgateway.authPassThroughAllowed = false
1276
- // end - mandatory plugin initialization
143
+ ## Installation
1277
144
 
1278
- HelperRest could included and used by REST plugins
145
+ ### Prerequisites
1279
146
 
1280
- Plugins should include following SCIM Gateway methods:
1281
-
1282
- * scimgateway.getUsers()
1283
- * scimgateway.createUser()
1284
- * scimgateway.deleteUser()
1285
- * scimgateway.modifyUser()
1286
- * scimgateway.getGroups()
1287
- * scimgateway.createGroup()
1288
- * scimgateway.deleteGroup()
1289
- * scimgateway.modifyGroup()
1290
-
1291
- In addition following general API methods are available for use:
1292
-
1293
- * scimgateway.postApi()
1294
- * scimgateway.putApi()
1295
- * scimgateway.patchApi()
1296
- * scimgateway.getApi()
1297
- * scimgateway.deleteApi()
1298
- * scimgateway.publicApi()
1299
-
1300
- In code editor (e.g., Visual Studio Code), method details and documentation are shown by IntelliSense
1301
-
1302
- ## License
1303
-
1304
- MIT © [Jarle Elshaug](https://www.elshaug.xyz)
1305
-
1306
- ## Change log
1307
-
1308
- ### v6.2.0
1309
-
1310
- [Fixed]
1311
-
1312
- - `helper-rest` failed on Bun v1.3.14 due to stricter compliance with Fetch standards.
1313
-
1314
- [Improved]
1315
-
1316
- - New `plugin-generic` replacing previous `plugin-scim`. This new plugin use the endpointMapper for flexible attribute mapping and also supports the new mapper option `valueMap` (e.g., group filtering and mapping). The default configuration uses one-to-one SCIM mapping, with plugin-loki as the target SCIM endpoint.
1317
- - endpointMapper now supports the 'valueMap' option
1318
-
1319
- Example configuration:
1320
-
1321
- "map": {
1322
- "group": {
1323
- ...
1324
- "displayName": {
1325
- "mapTo": "displayName",
1326
- "type": "string",
1327
- "valueMap": {
1328
- "outboundEndpointGrp1": "inboundScimGrp1",
1329
- "Employees": "Admins"
1330
- }
1331
- },
1332
- ...
1333
- }
1334
- ...
1335
- }
1336
-
1337
- Using the above settings restricts the client using SCIM Gateway with regard to group management.
1338
- The client will only see and be able to manage groups with SCIM names "inboundScimGrp1" and "Admins",
1339
- if their mapped counterparts exist at the target endpoint as "outboundEndpointGrp1" and "Employees".
1340
-
1341
- Use case:
1342
-
1343
- - Allowlisting specific groups or user objects that includes attribute mapping having the valueMap option configured.
1344
- - Supporting different inbound/outbound names (e.g., Entra ID group provisioning to SCIM Gateway).
1345
-
1346
-
1347
- ### v6.1.20
1348
-
1349
- [Fixed]
1350
-
1351
- - plugin-entra-id: Roles introduced in v6.1.19 were missing when retrieving a single user.
1352
-
1353
-
1354
- ### v6.1.19
1355
-
1356
- [Fixed]
1357
-
1358
- - SCIM v2.0 ResourceType endpoint schemas using incorrect id.
1359
-
1360
- [Improved]
1361
-
1362
- - SCIM Gateway now supports `GET /Roles` and `GET /Entitlements` endpoint requests, with corresponding user management via the standard SCIM `roles` and `entitlements` attributes.
1363
- - plugin-entra-id: Uses `entitlements` for Entra ID licenses (read-only) and `roles` for Entra ID Permanent and Eligible roles (full management).
1364
- - PIM Eligible roles requires API permissions `RoleEligiblitySchedule.ReadWrite.All`
1365
- - PIM Permanent roles requires API permissions `RoleManagement.ReadWrite.Directory`
1366
- - Remove mapping configuration `map.user.roles` if conditions not met (or use only the Eligible permissions set to Read if user role management is not needed).
1367
- - The `skipSignInActivity` option, introduced in v6.1.17, is no longer used. Instead, permissions for both `signInActivity` and PIM roles are validated at startup.
1368
-
1369
- ### v6.1.18
1370
-
1371
- [Fixed]
1372
-
1373
- - createUser and modifyUser returns full user object. Some endpoints like Entra ID hasn’t caught up yet due to internal sync and changes are not reflected. This update ensure returned user object contains what have been modified.
1374
-
1375
- ### v6.1.17
1376
-
1377
- [Fixed]
1378
-
1379
- - plugin-entra-id:
1380
-
1381
- - Fixed an issue where `filter=userName eq "user_upn"` was broken in v6.1.11 when using the updated configuration file that includes `map.user.signInActivity`.
1382
- - Added new configuration option `endpoint.entity.[baseEntity].skipSignInActivity = true` to exclude the `signInActivity` attribute. This attribute requires a Microsoft Entra ID Premium license and the `AuditLog.Read.All` API permission.
1383
-
1384
- ### v6.1.16
1385
-
1386
- [Improved]
1387
-
1388
- - plugin-entra-id: `GET /Entitlements` using derivedIncludes, fully flattened (recursive expansion of previous includes).
1389
-
1390
- ### v6.1.15
1391
-
1392
- [Fixed]
1393
-
1394
- - plugin-entra-id: fixed `filter=entitlements pr`
1395
-
1396
- ### v6.1.14
1397
-
1398
- [Improved]
1399
-
1400
- - Some cosmetics like supporting filter `attribute not pr`
1401
- - Dependencies bump
1402
-
1403
- ### v6.1.13
1404
-
1405
- [Improved]
1406
-
1407
- - plugin-entra-id: `signInActivity` attributes are filterable
1408
-
1409
-
1410
- ### v6.1.12
1411
-
1412
- [Improved]
1413
-
1414
- - filter operator `pr` (precense) now sent to plugin (previoulsy rejected)
1415
- - plugin-entra-id: now handles the pr filter operator for entitlements
1416
-
1417
- ### v6.1.11
1418
-
1419
- [Fixed]
1420
-
1421
- - From v6.1.6, schemas are autogenerated when using `endpointMapper` (configuration `map.user` and `map.group`). Fixed incorrect schema generation logic.
1422
-
1423
- [Improved]
1424
-
1425
- - New endpoint `GET /Entitlements` and corresponding new plugin method `scimgateway.getEntitlements()`, which is currently used by plugin-entra-id.
1426
- - plugin-entra-id: User license information through entitlements attribute.
1427
- - plugin-entra-id: The `plugin-entra-id.json` configuration file includes `map.user.signInActivity`. Using the `signInActivity` attribute requires an Entra ID Premium license and the API permission `AuditLog.Read.All`.
1428
- **Remove this mapping configuration if these conditions are not met**, otherwise provisioning will fail and errors such as `Authentication_RequestFromNonPremiumTenantOrB2CTenant` may occur.
1429
-
1430
- ### v6.1.10
1431
-
1432
- [Fixed]
1433
-
1434
- - plugin-entra-id: user group membership now includes nested (transitive) groups (`direct` and `indirect`)
1435
- - Docker example files `config/docker/.dockerignore` and `docker-compose-mssql.yml` were missing
1436
-
1437
- ### v6.1.9
1438
-
1439
- [Improved]
1440
-
1441
- - Some improvements to createUser/createGroup regarding the response object, which should contain the newly generated ID
1442
-
1443
- ### v6.1.8
1444
-
1445
- [Fixed]
1446
-
1447
- - Incorrect masking of secrets in the final info log message for requests
1448
-
1449
- ### v6.1.7
1450
-
1451
- [Fixed]
1452
-
1453
- - Incorrect masking of secrets in the final info log message for requests
1454
- - plugin-entra, fixed an issue where creating a user with a manager sometimes failed
1455
-
1456
- ### v6.1.6
1457
-
1458
- [Fixed]
1459
-
1460
- - plugin-loki and plugin-mongodb, using extension schema attributes in search returned empty result
1461
- - Auth validation failure because of readOnly protection now returns 405 instead of 401
1462
- - The post-install step now verifies and updates `package.json` to ensure the mandatory `"type": "module"` setting is applied. Using `npm init -y` instead of the recommended `bun init -y` sets `"type": "commonjs"` by default, which is incorrect.
1463
-
1464
- [Improved]
1465
-
1466
- - Using the endpointMapper configuration (`endpoint.map.user` / `endpoint.map.group`) will now generate a custom schema instead of using the default SCIM schema by `GET /Schemas`. In addition the configuration `endpoint.map` now supports a special `"x-agent-schema": {...}` configuration which is used by the schema generator for updating `description` and including AI MCP tools related instructions. See `plugin-entra-id.json` for examples.
1467
- - Dependencies bump
1468
-
1469
- ### v6.1.5
1470
-
1471
- [Improved]
1472
-
1473
- - complex filtering (and/or) now handled by scimgateway using plugin's simple filtering logic
1474
- - modify group response now returns http status 204 (No Content) instead of 200 OK (full group object)
1475
- - url `/auth` can now be used for validating external authentication
1476
- - plugin-entra-id, now supports filter `sw` (startsWith)
1477
-
1478
-
1479
- ### v6.1.4
1480
-
1481
- [Fixed]
1482
-
1483
- - plugin-entra-id, OData paging was not working, so some users/groups/members might be missing
1484
- - helper-rest, OData paging
1485
- - user’s group membership did not iterate through paging and may be incomplete
1486
-
1487
- ### v6.1.3
1488
-
1489
- [Fixed]
1490
-
1491
- - azure relay, recover on failure
1492
- - plugin-ldap, some improvements for Active Directory and the use of objectGUID/mS-DS-ConsistencyGuid
1493
- - plugin-mongodb, group meta.version not standarized
1494
- - when modifying group members, if an error occurs, the gateway now checks whether it was caused by adding an existing member or removing a non-existing member. In such cases, it returns 200 OK instead of an error.
1495
-
1496
- [Improved]
1497
- - Dependencies bump
1498
-
1499
- ### v6.1.2
1500
-
1501
- [Fixed]
1502
-
1503
- - SMTP mail functionality failed because of an updated dependency
1504
- - endpointMapper failed when `mapTo` included multiple comma-separated attributes and one of them was a multivalued attribute, e.g. `{ "mail": { "mapTo": "userName,emails.work.value" } }`
1505
-
1506
- ### v6.1.1
1507
-
1508
- [Fixed]
1509
-
1510
- - plugin-ldap, a createUser operation followed immediately by a readUser (automatically performed by SCIM Gateway) may not find the newly created user on some systems, such as Samba AD, due to timing issues
1511
-
1512
-
1513
- [Improved]
1514
-
1515
- - the final info log message now includes a JSON serialization of all elements, such as durationMs, status, requestBody, responseBody, ...
1516
-
1517
- ### v6.1.0
1518
-
1519
- [Improved]
1520
-
1521
- - `tsx` is now included, allowing SCIM Gateway to run as an ES module (TypeScript) in Node.js. The mandatory plugin section, which previously required complex dynamic loading, can now be simplified using static imports
1522
-
1523
- **Old plugin-xxx.ts:**
1524
-
1525
- // start - mandatory plugin initialization
1526
- const ScimGateway: typeof import('scimgateway').ScimGateway = await (async () => {
1527
- try {
1528
- return (await import('scimgateway')).ScimGateway
1529
- } catch (err) {
1530
- const source = './scimgateway.ts'
1531
- return (await import(source)).ScimGateway
1532
- }
1533
- })()
1534
- const scimgateway = new ScimGateway()
1535
- const config = scimgateway.getConfig()
1536
- scimgateway.authPassThroughAllowed = false
1537
- // end - mandatory plugin initialization
1538
-
1539
- **New plugin-xxx.ts:**
1540
-
1541
- // start - mandatory plugin initialization
1542
- import { ScimGateway } from 'scimgateway'
1543
- const scimgateway = new ScimGateway()
1544
- const config = scimgateway.getConfig()
1545
- scimgateway.authPassThroughAllowed = false
1546
- // end - mandatory plugin initialization
1547
-
1548
-
1549
- **Old Node.js startup:**
1550
-
1551
- node --experimental-strip-types c:\scimgateway\index.ts // scimgateway downloaded from github
1552
-
1553
- **New Node.js startup:**
1554
-
1555
- node --import=tsx ./index.ts // running in local package
1556
-
1557
- - index.ts now using static import instead of dynamic
1558
-
1559
- **Old index.ts:**
1560
-
1561
- const plugins = ['loki']
1562
- for (const plugin of plugins) {
1563
- try {
1564
- await import(`./lib/plugin-${plugin}.ts`)
1565
- } catch (err: any) {
1566
- console.error(err)
1567
- }
1568
- }
1569
-
1570
- **New index.ts:**
1571
-
1572
- // start one or more plugins:
1573
- // import './lib/plugin-scim.ts'
1574
- // import './lib/plugin-entra-id.ts'
1575
- // import './lib/plugin-ldap.ts'
1576
- // import './lib/plugin-mongodb.ts'
1577
- // import './lib/plugin-api.ts'
1578
- // import './lib/plugin-mssql.ts'
1579
- // import './lib/plugin-saphana.ts'
1580
- // import './lib/plugin-soap.ts'
1581
-
1582
- import './lib/plugin-loki.ts'
1583
- export {}
1584
-
1585
- - Bun binary build is now supported allowing SCIM Gateway to be compiled into a single executable binary for simplified deployment and execution. The binary must have the same name (prefix) as the configuration file in the config directory, and this directory must be located in the same folder as the binary.
1586
-
1587
- cd my-scimgateway
1588
- bun build --compile ./lib/plugin-loki.ts --target=bun-darwin-arm64 --outfile ./build/plugin-loki
1589
- # for target options, see: https://bun.com/docs/bundler/executables#cross-compile-to-other-platforms
1590
-
1591
- cp -r ./config ./build
1592
- # build directory now ready for production deployment
1593
- cd build
1594
- # run the binary - note, binary must have same name (prefix) as the configuration file in the config directory
1595
- ./plugin-loki
1596
-
1597
- - Dependencies bump
1598
-
1599
- ### v6.0.2
1600
-
1601
- [Fixed]
1602
- - Gateway now passing provided filter attributes for getUsers()/getGroups to plugin instead of using empty array for having all supported attributes returned
1603
-
1604
- ### v6.0.1
1605
-
1606
- [Fixed]
1607
- - plugin-ldap, failed when the RDN value contained the character '=' e.g., `CN=Firstname \= Lastname,CN=Users,DC=my-company,DC=com`
1608
- - GET using filter failed when filter value contained the character '%' e.g., `GET /Users?filter=userName eq "my % name"`
1609
-
1610
- ### v6.0.0
1611
-
1612
- **[MAJOR]**
1613
-
1614
- - API method response bodies (no SCIM related) will now be returned "as-is". Previously response body had format `{ result: <content> }`. If response body is parsed by client, client must be changeed to reflect the new response body format.
1615
- - New plugin API method `scimgateway.publicApi()` for handling public path `/pub/api` with no authentication required, please see `plugin-api`
1616
- e.g. `GET /pub/api?model=Tesla`
1617
- - Configuration `scimgateway.auth.bearerJwtAzure` is no longer supported. Instead use the new `scimgateway.auth.bearerJwt.azureTenantId` for allowing Entra ID initiated provisioning through scimgateway
1618
-
1619
- **Old configuration:**
1620
-
1621
- "bearerJwtAzure": [
1622
- {
1623
- "tenantIdGUID": {entra-tenant-id},
1624
- "readOnly": false,
1625
- "baseEntities": []
1626
- }
1627
- ],
1628
-
1629
- **New configuration:**
1630
-
1631
- "bearerJwt": [
1632
- {
1633
- "secret": null,
1634
- "publicKey": null,
1635
- "wellKnownUri": null,
1636
- "azureTenantId": {entra-tenant-id},
1637
- "options": {
1638
- "issuer": null
1639
- },
1640
- "readOnly": false,
1641
- "baseEntities": []
1642
- }
1643
- ],
1644
-
1645
- - All existing configurations having key `tenantIdGUID` must be replaced with the new key `azureTenantId`. This also applies to endpoint configuration used by HelperRest()
1646
-
1647
- **Old configuration:**
1648
-
1649
- "email": {
1650
- "auth": {
1651
- "type": "oauth",
1652
- "options": {
1653
- "tenantIdGUID": null,
1654
- "clientId": null,
1655
- "clientSecret": null
1656
- }
1657
- },
1658
-
1659
- **New configuration:**
1660
-
1661
- "email": {
1662
- "auth": {
1663
- "type": "oauth",
1664
- "options": {
1665
- "azureTenantId": null,
1666
- "clientId": null,
1667
- "clientSecret": null
1668
- }
1669
-
1670
-
1671
- Example of HelperRest() endpoint configuration used by plugin-entra-id having tenantIdGUID replaced with azureTenantId:
1672
-
1673
- "connection": {
1674
- "baseUrls": [],
1675
- "auth": {
1676
- "type": "oauth",
1677
- "options": {
1678
- "azureTenantId": "Entra ID Tenant ID (GUID)",
1679
- "clientId": "Entra ID Application ID",
1680
- "clientSecret": "Entra ID Application secret value"
1681
- }
1682
- },
1683
-
1684
- ### v5.5.5
1685
-
1686
- [Improved]
1687
- - Dependencies bump
1688
- - Docker - `.dockerignore` included at root, same as `./config/docker/.dockerignore`
1689
-
1690
- ### v5.5.4
1691
-
1692
- [Fixed]
1693
- - Docker - exclude any package postinstall script to be run `--ignore-scripts`, because of `bun pm trust` prerequirement
1694
-
1695
- ### v5.5.3
1696
-
1697
- [Fixed]
1698
- - Docker - fixed `docker build` error introduced in v5.5.0 (using bun.lock instead of binary bun.lockb)
1699
-
1700
- [Improved]
1701
- - plugin-mssql - attribute externalId included
1702
- - .dockerignore - new docker configuration file, contains files to be excluded from the build context
1703
-
1704
- ### v5.5.2
1705
-
1706
- [Improved]
1707
-
1708
- - Entra ID Federated Identity Credentials introduced in v5.5.0, the issuer configuration should be scimgateway base URL
1709
- old: `"issuer": "<https://FQDN-scimgateway>/oauth"`
1710
- new: `"issuer": "<https://FQDN-scimgateway>"`
1711
-
1712
- Change log v5.5.0 have been corrected with the new issuer having base URL only
1713
-
1714
-
1715
- ### v5.5.1
1716
-
1717
- [Fixed]
1718
-
1719
- - 401 Unauthorized response did include scim-formatted error message when using `helper-rest` and authentication `PassThrough`. 401 should not include scim-formatted error message
1720
-
1721
- ### v5.5.0
1722
-
1723
- [Improved]
1724
-
1725
- - Entra ID [Federated Identity Credentials](https://learn.microsoft.com/en-us/graph/api/resources/federatedidentitycredentials-overview?view=graph-rest-1.0) is now supported. Identity federation allows SCIM Gateway to access Microsoft Entra protected resources without needing to manage secrets
1726
-
1727
- helper-rest includes options for federated credentials:
1728
-
1729
- "auth {
1730
- "type": "oauthJwtBearer",
1731
- "options": {
1732
- "tenantIdGUID": "<Entra ID tenantIdGUID",
1733
- "fedCred": {
1734
- "issuer": "<https://FQDN-scimgateway>",
1735
- "subject": "<entra id application object id - client id>",
1736
- "name": "<entra id federated credentials unique name>"
1737
- }
1738
- }
1739
- }
1740
-
1741
- Example:
1742
-
1743
- "auth {
1744
- "type": "oauthJwtBearer",
1745
- "options": {
1746
- "tenantIdGUID": "11111111-2222-3333-4444-555555555555",
1747
- "fedCred": {
1748
- "issuer": "https://scimgateway.my-company.com",
1749
- "subject": "99999999-8888-7777-6666-555555555555",
1750
- "name": "plugin-entra-id"
1751
- }
1752
- }
1753
- }
1754
-
1755
- Note: Federated credentials (scenario "Other issuer") defined for the application in Entra ID must match the corresponding `issuer`, `subject`, and `name` values defined in the SCIM Gateway endpoint configuration. An example of this can be using `plugin-entra-id` and other plugins that interact with endpoints or applications protected by Entra ID.
1756
-
1757
- Also note: SCIM Gateway must be reachable from the internet (as defined by the `issuer` URL). This requires allowing inbound internet communication — or alternatively, Azure Relay can be used for outbound-only communication.
1758
-
1759
- ### v5.4.4
1760
-
1761
- [Improved]
1762
-
1763
- - External JWKS (JSON Web Key Set) is now supported by JWT Authentication. These are public and typically frequent rotated by modern identity providers
1764
-
1765
- JKWS is enabled by setting scimgateway.auth.bearerJwt[].wellKnownUri to the identity provider's well-known URI
1766
-
1767
- Keycloak example:
1768
-
1769
- auth: {
1770
- "bearerJwt": [
1771
- {
1772
- "wellKnownUri": "https://keycloak.example.com/realms/example-realm/.well-known/openid-configuration",
1773
- "options": {
1774
- ...
1775
- },
1776
- ...
1777
- }
1778
- ]
1779
- }
1780
-
1781
- ### v5.4.3
1782
-
1783
- [Fixed]
1784
-
1785
- - helper-rest, fixed an issue introduced in v5.3.8 that caused problems using OAuth
1786
-
1787
- [Improved]
1788
-
1789
- - Remote real-time logger
1790
-
1791
- ### v5.4.2
1792
-
1793
- [Improved]
1794
-
1795
- - baseEntity included as json-key in logs
1796
- - 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.
1797
-
1798
- 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.
1799
-
1800
- ### v5.4.1
1801
-
1802
- [Improved]
1803
-
1804
- - Remote real-time logger, stop/start button added when using browser
1805
-
1806
- ### v5.4.0
1807
-
1808
- [Improved]
1809
-
1810
- - 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
1811
-
1812
- ### v5.3.8
1813
-
1814
- [Improved]
1815
-
1816
- - [Azure Relay](https://learn.microsoft.com/en-us/azure/azure-relay/relay-what-is-it) is now supported for secure and hassle-free outbound communication — with just one minute of configuration
1817
-
1818
- Using Azure technology we have different options for setting up a communication tunnel to SCIM Gateway:
1819
-
1820
- `Microsoft Entra Application Proxy + Microsoft Entra Application Proxy Connector` (SCIM Gateway located on-premises or using Azure private VNet/IP)
1821
- `Azure Application Gateway` - Layer 7 (SCIM Gateway located in Azure)
1822
- `Azure Relay` (SCIM Gateway located on-premises or in Azure)
1823
-
1824
- Azure pricing for using Azure Relay is approx. 10$ per month for each listener (SCIM Gateway plugin)
1825
-
1826
- **Using out-of-the-box Azure Relay:**
1827
-
1828
- Prerequisite: SCIM Gateway having outbound internet access (https/443)
1829
- In Azure create a `Relay` - `<namespace-name>`
1830
- In the Relay, create an entity of type `Hybrid Connection` - `<hybrid-connection-name>` **one for each SCIM Gateway plugin**
1831
- The `Requires Client Authorization` option **should be unchecked (not activated)**, unless we are using custom IdP/API having logic for including SAS-token in the communication header
1832
- Shared access policies - RootManageSharedaccessKey - Primary Key (copy this one)
1833
-
1834
- SCIM Gateway plugin configuration:
1835
-
1836
- {
1837
- "scimgateway: {
1838
- ...
1839
- "azureRelay": {
1840
- "enabled": true,
1841
- "connectionUrl": "https://<namespace-name>.servicebus.windows.net/<hybrid-connection-name>",
1842
- "apiKey": "<primary-key>"
1843
- },
1844
- ...
1845
- },
1846
- ...
1847
- }
1848
-
1849
- `connectionUrl` will be the SCIM base URL used by IdP/API for accessing SCIM Gateway
1850
-
1851
- Example:
1852
- GET `https://<namespace-name>.servicebus.windows.net/<hybrid-connection-name>/Users`
1853
- GET `https://<namespace-name>.servicebus.windows.net/<hybrid-connection-name>/<baseEntity>/Users`
1854
-
1855
- If several SCIM Gateway´s (same plugin) connect listeners using the same Azure Relay connectionUrl, there will be load-balancing and round-robin distribution
1856
-
1857
- ### v5.3.7
1858
-
1859
- [Improved]
1860
-
1861
- - Normalize line endings to LF
1862
-
1863
- ### v5.3.6
1864
-
1865
- [Fixed]
1866
-
1867
- - Some minor ETag improvements
1868
-
1869
- ### v5.3.5
1870
-
1871
- [Improved]
1872
-
1873
- - ETag now supported and default included for all requests. Plugin may use custom ETag by returning meta.version.
1874
-
1875
- ### v5.3.4
1876
-
1877
- [Fixed]
1878
-
1879
- - PATCH operations (modifyUser/modifyGroup) that includes `null` values, will now be converted to empty string `""`
1880
-
1881
- {
1882
- "schemas": [
1883
- "urn:ietf:params:scim:api:messages:2.0:PatchOp"
1884
- ],
1885
- "Operations": [{
1886
- "op": "replace",
1887
- "value": {
1888
- "name": {
1889
- "formatted": "Smith, John",
1890
- "honorificPrefix": null
1891
- }
1892
- }}
1893
- ]
1894
- }
1895
-
1896
- In the example above, following will be sent to plugin:
1897
- { "name": { "formatted": "Smith, John", "honorificPrefix": "" } }
1898
-
1899
- ### v5.3.3
1900
-
1901
- [Fixed]
1902
-
1903
- - helper-rest, SamlBearer token-request now includes `new_token=true` to avoid retrieving an existing token that is about to expire
1904
-
1905
- ### v5.3.2
1906
-
1907
- [Improved]
1908
-
1909
- - helper-rest, retry on request error 504 Gateway Timeout
1910
- - performance micro-optimization on log mask logic
1911
-
1912
- ### v5.3.1
1913
-
1914
- [Fixed]
1915
-
1916
- - Incorrect log masking of SCIM 2.0 PATCH Operations
1917
- - plugin-ldap, create user/group having DN special character `#` failed on OpenLDAP
1918
-
1919
- ### v5.3.0
1920
-
1921
- [Improved]
1922
-
1923
- - [Bulk Operations](https://datatracker.ietf.org/doc/html/rfc7644#section-3.7) now supported
1924
- - Dependencies bump
1925
-
1926
- ### v5.2.5
1927
-
1928
- [Fixed]
1929
-
1930
- - endpointMapper (used by plugin-entra-id and plugin-ldap) in v5 when using mapping type=array, the first element was excluded on outbound mapping in some use cases
1931
-
1932
- ### v5.2.4
1933
-
1934
- [Improved]
1935
-
1936
- - New configuration `log.logDirectory` for custom defined log directory e.g. `/var/log/scimgateway` that will override default `<scimgateway path>/logs`.
1937
- **Thanks to [@Gerrit Lansing](https://github.com/gerritlansing)**
1938
- - Base URL like `/scim/v1` and `/scim/v2` is now supported, also with baseEntity e.g. `/scim/v2/client1/Users`
1939
-
1940
- ### v5.2.3
1941
-
1942
- [Fixed]
1943
-
1944
- - GET /ResourceTypes was missing in v5
1945
-
1946
- ### v5.2.2
1947
-
1948
- [Fixed]
1949
-
1950
- - plugin-ldap, tls configuration now supported for Bun > v1.2.4, previously environments had to be used
1951
-
1952
- "tls": {
1953
- "ca": "ca-file-name", // located in config/certs
1954
- "rejectUnauthorized": true
1955
- }
1956
-
1957
- [Improved]
1958
-
1959
- - Dependencies bump
1960
-
1961
- ### v5.2.1
1962
-
1963
- [Fixed]
1964
-
1965
- - Logger did not use the correct plugin rollover filename when the gateway ran multiple plugins
1966
-
1967
- ### v5.2.0
1968
-
1969
- [Improved]
1970
-
1971
- - Logger have been redesigned
1972
-
1973
- Supports console, file and push (client subscriber) logging
1974
- Remote real-time log subscription, see configuration notes
1975
- JSON formatted log messages
1976
- UTC (Coordinated Universal Time)
1977
- File logging will rotate on startup
1978
- File logging now includes configuration options for maxFiles and maxSize
1979
- Console using default colorized and minimized output. If redirecting stdout/stderr, standard JSON will be used and no color encoding
1980
-
1981
-
1982
- ### v5.1.8
1983
-
1984
- [Fixed]
1985
-
1986
- - plugin-ldap, dn that includes double underscore `__` not correctly handled
1987
-
1988
-
1989
- ### v5.1.7
1990
-
1991
- [Fixed]
1992
-
1993
- - Using gateway certificate CA, the CA did not load correctly. It now also supports an array of multiple CAs.
1994
-
1995
- [Improved]
1996
-
1997
- - Dependencies bump
1998
-
1999
- ### v5.1.6
2000
-
2001
- [Improved]
2002
-
2003
- - HelperRest, payload/claims configuration now defined in auth.options.jwtPayload and auth.options.samlPayload. Previously all was defiend in auth.options
2004
- - README configuration notes updated
2005
-
2006
- ### v5.1.5
2007
-
2008
- [Improved]
2009
-
2010
- - 404 NOT_FOUND is now logged as a warning instead of error
2011
-
2012
- ### v5.1.4
2013
-
2014
- [Fixed]
2015
-
2016
- - Postinstall failed using the new Bun v1.2.0
2017
-
2018
- ### v5.1.3
2019
-
2020
- [Fixed]
2021
-
2022
- - HelperRest, auth.type=`oauthJwtBearer` and auth.options=`tenantIdGUID`
2023
-
2024
- Configuration example using Entra ID application having uploaded cert.pem as certificate secret:
2025
-
2026
- "endpoint": {
2027
- "entity": {
2028
- "undefined": {
2029
- "connection": {
2030
- "baseUrls": [],
2031
- "auth": {
2032
- "type": "oauthJwtBearer",
2033
- "options": {
2034
- "tenantIdGUID": "Entra ID Tenant ID (GUID)",
2035
- "clientId": "<application clientId>",
2036
- "tls": { // files located in ./config/certs
2037
- "key": "key.pem",
2038
- "cert": "cert.pem"
2039
- }
2040
- }
2041
- }
2042
- }
2043
- }
2044
- }
2045
- }
2046
-
2047
- Please see code editor method HelperRest doRequest() IntelliSense for details
2048
-
2049
- Note, this fix may break `plugin-entra-id` if baseUrls configuration not empty. If baseUrl not empty, it will be used. If empty, baseUrl will automatically be set according to graph api when using tenantIdGUID definition
2050
-
2051
- ### v5.1.2
2052
-
2053
- [Improved]
2054
-
2055
- - Simplified some initialization logic
2056
-
2057
- ### v5.1.1
2058
-
2059
- [Fixed]
2060
-
2061
- - SCIM Gateway failed to start on linux using Bun >= v1.1.43
2062
-
2063
- ### v5.1.0
2064
-
2065
- [Improved]
2066
-
2067
- - By configuring the `chainingBaseUrl`, it is now possible to chain multiple gateways in sequence, such as `gateway1->gateway2->gateway3->endpoint`. In this setup, gateway beave much like a reverse proxy, validating authorization at each step unless PassThrough mode is enabled. Chaining is also supported in stream subscriber mode
2068
-
2069
- Please see `Configuration notes` for details
2070
-
2071
-
2072
- ### v5.0.15
2073
-
2074
- [Improved]
2075
-
2076
- - HelperRest, auth.type=oauthSamlAssertion and auth.type=oauthJwtAssertion have been updated to `oauthSamlBearer` and `oauthJwtBearer` for consistency
2077
-
2078
- ### v5.0.14
2079
-
2080
- [Improved]
2081
-
2082
- - email now supports Google Workspace Gmail using REST OAuth
2083
- - email workaround for ExO national characters introduced in v5.0.7 not needed anymore - ExO/GraphApi seems to have been fixed
2084
- - some minor cosmetics on email message layout formatting when using plain text message
2085
- - HelperRest now includes authentication type `oauthJwtAssertion`
2086
-
2087
- ### v5.0.13
2088
-
2089
- [Improved]
2090
-
2091
- - scim-stream, using the new reorganized nats.js v3 client library
2092
- - cosmetics, `use strict` not needed and removed because ES modules are always strict mode
2093
-
2094
- ### v5.0.12
2095
-
2096
- [Fixed]
2097
-
2098
- - HelperRest doRequest() incorrect Auth PassThrough handling
2099
-
2100
- [Improved]
2101
-
2102
- - Dependencies bump
2103
-
2104
-
2105
- ### v5.0.11
2106
-
2107
- [Fixed]
2108
-
2109
- - OAuth token response on error missing error_description in v5
2110
- - HelperRest doRequest() now also includes retry logic on invalid token that has not expired - will renew token
2111
-
2112
- ### v5.0.10
2113
-
2114
- [Improved]
2115
-
2116
- - OAuth token request now accept missing or invalid Content-Type header
2117
-
2118
- ### v5.0.9
2119
-
2120
- [Improved]
2121
-
2122
- - HelperRest doRequest() now support configuration auth type `oauthSamlAssertion` for OAuth SAML token assertion. Please see code editor method IntelliSense for details
2123
-
2124
- ### v5.0.8
2125
-
2126
- [Fixed]
2127
-
2128
- - Ensure Bun compatibility with Azure Reverse Proxy for large and long running response
2129
- - HelperRest was not compatible with Node.js
2130
- - plugin-mssql, some error handling should not throw an error
2131
- - Configuration files updated according to the v5 configuration syntax of `scimgateway.auth.bearerOAuth` - `clientId/clientSecret` now replacing deprecated `client_id/client_secret`
2132
-
2133
- ### v5.0.7
2134
-
2135
- [Improved]
2136
-
2137
- - plugin-mssql all methods now implemented, also includes docker and dbinit configuration, **thanks to [@Peter Havekes](https://github.com/phavekes) and [@mrvanes](https://github.com/mrvanes)**
2138
-
2139
- [Fixed]
2140
-
2141
- - mail sending option introduced in v5.0.6 did not fully support national special charcters when using Microsoft Exchange Online and html formatted email
2142
-
2143
- ### v5.0.6
2144
-
2145
- [Improved]
2146
-
2147
- - new configuration option: `scimgateway.idleTimeout` default 120, sets the the number of seconds to wait before timing out a connection due to inactivity
2148
- - deprecated configuration option: `scimgateway.payloadSize` Bun using default maxRequestBodySize 128MB
2149
- - new configuration option: `scimgateway.email` replacing legacy `scimgateway.emailOnError` (legacy still supported). Email now support oauth authentication
2150
-
2151
- **old configuration:**
2152
-
2153
- {
2154
- "scimgateway": {
2155
- ...
2156
- "emailOnError": {
2157
- "smtp": {
2158
- "enabled": false,
2159
- "host": null,
2160
- "port": 587,
2161
- "proxy": null,
2162
- "authenticate": true,
2163
- "username": null,
2164
- "password": null,
2165
- "sendInterval": 15,
2166
- "to": null,
2167
- "cc": null
2168
- }
2169
- },
2170
- ...
2171
- },
2172
- ...
2173
- }
2174
-
2175
-
2176
- **new configuration:**
2177
- Using Microsoft Exchange Online and oauth authencation which also is default and recommended by Microsoft. For other mail servers and options like SMTP AUTH (basic/oauth), please see configuration description. Plugin may also send mail using method scimgateway.sendMail()
2178
-
2179
- {
2180
- "scimgateway": {
2181
- ...
2182
- "email": {
2183
- "auth": {
2184
- "type": "oauth",
2185
- "options": {
2186
- "tenantIdGUID": null,
2187
- "clientId": null,
2188
- "clientSecret": null
2189
- }
2190
- },
2191
- "emailOnError": {
2192
- "enabled": false,
2193
- "from": null,
2194
- "to": null
2195
- }
2196
- },
2197
- ...
2198
- },
2199
- ...
2200
- }
2201
-
2202
- Configuration notes when using oauth and tenantIdGUID - Microsoft Exchange Online (ExO):
2203
-
2204
- - Entra ID application must have application permissions `Mail.Send`
2205
- - To prevent the sending of emails from any defined mailboxes, an ExO `ApplicationAccessPolicy` must be defined through PowerShell.
2206
-
2207
- First create a mail-enabled security-group that only includes those users (mailboxes) the application is allowed to send from
2208
- Note, `mail enabled security group` cannot be created from portal, only from admin or admin.exchange console
2209
-
2210
- ##Connect to Exchange
2211
- Install-Module -Name ExchangeOnlineManagement
2212
- Connect-ExchangeOnline
2213
-
2214
- ##Create ApplicationAccessPolicy
2215
- New-ApplicationAccessPolicy -AppId <AppClientID> -PolicyScopeGroupId <MailEnabledSecurityGrpId> -AccessRight RestrictAccess -Description "Restrict app to specific mailboxes"
2216
-
2217
-
2218
- ### v5.0.5
2219
-
2220
- [Fixed]
2221
-
2222
- - plugin-ldap, dn special character not correct for ascii code 128(dec)/80(hex)
2223
-
2224
- ### v5.0.4
2225
-
2226
- [Improved]
2227
-
2228
- - minor type definition cosmetics
2229
-
2230
- ### v5.0.3
2231
-
2232
- [Fixed]
2233
-
2234
- - unauthorized connection when using configuration bearerJwtAzure
2235
-
2236
- [Improved]
2237
-
2238
- - minor type definition cosmetics
2239
-
2240
-
2241
- ### v5.0.2
2242
-
2243
- [Improved]
2244
-
2245
- - minor cosmetics readme updates
2246
-
2247
- ### v5.0.1
2248
-
2249
- [Fixed]
2250
-
2251
- - postinstall did not update index.ts when default bun index.ts did exist
2252
-
2253
-
2254
- ### v5.0.0
2255
-
2256
- **[MAJOR]**
2257
-
2258
- Major version v5.0.0 marks a shift to native TypeScript support and prioritizes [Bun](https://bun.sh/) over Node.js.
2259
-
2260
- Besides going from JavaScript to TypeScript, following can be mentioned:
2261
-
2262
- * Code editor now having IntelliSense showing available methods and documentation details for scimgateway methods
2263
- * index.ts having new logic for starting plugins e.g.: `const plugins = ['ldap']` for starting plugin-ldap
2264
- * If using Node.js: node must be version >= 22.6.0, scimgateway must be downloaded from github (because stripping types is currently unsupported for files under node_modules) and startup argument `--experimental-strip-types` e.g.; `node --experimental-strip-types index.ts`
2265
- * Plugins can use `scimgateway.HelperRest()` for REST functionality. Previously this logic was included in each plugin that used REST.
2266
-
2267
- // start - mandatory plugin initialization
2268
- ...
2269
- const HelperRest: typeof import('scimgateway').HelperRest = await (async () => {
2270
- try {
2271
- return (await import('scimgateway')).HelperRest
2272
- } catch (err) {
2273
- const source = './scimgateway.ts'
2274
- return (await import(source)).HelperRest
2275
- }
2276
- })()
2277
- ...
2278
- // end - mandatory plugin initialization
2279
-
2280
- Note, HelperRest use fetch which is not fully supported by Node.js regarding TLS.
2281
- For TLS and Node.js, environment must instead be used and set before started, e.g.,:
2282
- `export NODE_EXTRA_CA_CERTS=/package-path/config/certs/ca.pem`
2283
- or
2284
- `export NODE_TLS_REJECT_UNAUTHORIZED=0`
2285
-
2286
- * Configuration secrets (password, secret, token, client_secret, ... ) defined in the `endpoint` section of the configuration file, will automatically be encrypted/decrypted. If there are secrets not handled by the automated encryption/decryption, we may use `scimgateway.getSecret()`. In the old version, corresponding method was named scimgateway.getPassword().
2287
- * kubernetes configuration and logic have been removed. Kubernetes can use default `/ping` url for healthchecks, and graceful shutdown is taken care of the gateway
2288
- * In case using custom schemas defined in lib/scimdef-v1/v2.js, these files have now changed to scimdef-v1/v2.json
2289
- * `config/docker/Dockerfile` now using Bun
2290
- * plugin-entra, modify licenses/servicePlans is not included anymore, only listing. For license management we instead use groups.
2291
- * plugin-ldap, for LDAPS/TLS and Bun, we must use environments e.g.:
2292
- `export NODE_EXTRA_CA_CERTS=/package-path/config/certs/ca.pem`
2293
- or
2294
- `export NODE_TLS_REJECT_UNAUTHORIZED=0`
2295
-
2296
-
2297
- **How to migrate existing plugins:**
2298
-
2299
- * Remove old index.js, use the new index.ts and update `const plugins = ['xxx']` to include your plugin name(s)
2300
- * Rename plugin-xxx.js to plugin-xxx.ts
2301
- * import must be used instead of require for loading modules e.g.:
2302
- const Loki = require('lokijs') => `import Loki from 'lokijs'`
2303
- * Use the new mandatory settings:
2304
-
2305
- // start - mandatory plugin initialization
2306
- const ScimGateway: typeof import('scimgateway').ScimGateway = await (async () => {
2307
- try {
2308
- return (await import('scimgateway')).ScimGateway
2309
- } catch (err) {
2310
- const source = './scimgateway.ts'
2311
- return (await import(source)).ScimGateway
2312
- }
2313
- })()
2314
- const scimgateway = new ScimGateway()
2315
- const config = scimgateway.getConfig()
2316
- scimgateway.authPassThroughAllowed = false
2317
- // end - mandatory plugin initialization
2318
-
2319
- * Use the new `config` object (mentioned above) which contains the `scimgatway.endpoint` configuration having automated encryption/decryption of any attributes named password, secret, client_secret, token and APIKey
2320
- * The old scimgateway.getPassword() is not normally not needed because of scimgateway automated `config` logic. If needed, use the new scimgateway.getSecret().
2321
- * Use the new logging syntax:
2322
-
2323
- replace: scimgateway.logger.debug(`${pluginName}[${baseEntity}] xxx`)
2324
- with: scimgateway.logDebug(baseEntity, `xxx`)
2325
-
2326
- * Use scimgateway.HelperRest() for REST functionlity, also supports Auth PassThrough
2327
- * scimgateway.endpointMapper() may be used for inbound/outbound attribute mappings
2328
- * In general when using TypeScript, variables should be type-defined: `let isDone: boolean = false`, `catch (err: any)`, ...
2329
-
2330
- ### v4.5.12
2331
-
2332
- [Improved]
2333
-
2334
- - plugin-ldap, new configuration { allowModifyDN: true } allows DN being changed based on modified mapping or namingAttribute
2335
-
2336
- ### v4.5.11
2337
-
2338
- [Improved]
2339
-
2340
- - deleteUser will try to revoke user from groups before deleting user
2341
- - advanced or-filter (e.g., used by One Identity Manager) will be chunked and handled by scimgateway as separate calls to plugin
2342
- - baseEntity now included in scimgateway log entries like plugin log entries
2343
-
2344
- [Fixed]
2345
-
2346
- - plugin-ldap, using OpenLDAP - configuration { "isOpenLdap": true } and adding an already existing group member returned 500 Error instead of 200 OK.
2347
- - plugin-ldap, using OpenLDAP in combination with endpoint user mapping `"type":"array"` and `"typeInbound":"string"` for handling comma separated SCIM string mapping towards an endpoint array/multivalue attribute, did not return correct sort order of the comma separated string when using OpenLDAP. Mapping example:
2348
-
2349
- "<endpointAttr>": {
2350
- "mapTo": "<scimAttr>",
2351
- "type": "array",
2352
- "typeInbound": "string"
2353
- },
2354
-
2355
-
2356
- ### v4.5.10
2357
-
2358
- [Fixed]
2359
-
2360
- - PUT changes introduced in v4.5.7 had incorrect check of configuration groupMemberOfUser (default not set)
2361
-
2362
- ### v4.5.9
2363
-
2364
- [Improved]
2365
-
2366
- - Dependencies bump
2367
-
2368
- ### v4.5.8
2369
-
2370
- [Fixed]
2371
-
2372
- - plugin-ldap failed when using national special characters and some other LDAP special characters in DN
2373
-
2374
- Note, plugin-ldap now has following new configuration:
2375
-
2376
- "ldap": {
2377
- "isOpenLdap": false,
2378
- ...
2379
- "namingAttribute": {
2380
- "user": [
2381
- {
2382
- "attribute": "CN",
2383
- "mapTo": "userName"
2384
- }
2385
- ],
2386
- "group": [
2387
- {
2388
- "attribute": "CN",
2389
- "mapTo": "displayName"
2390
- }
2391
- ]
2392
- },
2393
- ...
2394
- }
2395
-
2396
- `isOpenLdap` true/false decides whether or not OpenLDAP Foundation protocol should be used for national characters and special characters in DN. For Active Directory, default isOpenLdap=false should be used.
2397
-
2398
- `namingAttribute` can now be linked to scim `mapTo` attribute and is not hardcoded like it was in previous version.
2399
-
2400
- Previous `userNamingAttr` and `groupNamingAttr` shown below, is now deprecated
2401
-
2402
- "ldap": {
2403
- ...
2404
- "userNamingAttr": "CN",
2405
- "groupNamingAttr": "CN",
2406
- ...
2407
- }
2408
-
2409
-
2410
- ### v4.5.7
2411
-
2412
- [Fixed]
2413
-
2414
- - PUT changes introduced in v4.4.6 did not handle PUT /Groups correctly
2415
-
2416
- [Improved]
2417
- - configuration scim.usePutGroupMemberOfUser replaced by scim.groupMemberOfUser
2418
- - misc cosmetics
2419
-
2420
- ### v4.5.6
2421
-
2422
- [Improved]
2423
-
2424
- - plugin-ldap preserve multivalue-attribute order on modify. Do not apply to groups/members.
2425
-
2426
- ### v4.5.5
2427
-
2428
- [Fixed]
2429
-
2430
- - PUT /Groups/xxx failed on final group lookup and returned error
2431
- - endpointMapper failed to correctly map customExtensions in certain use cases
2432
-
2433
- ### v4.5.4
2434
-
2435
- [Fixed]
2436
-
2437
- - Delete User missing url-decoding of id e.g. using ldap-dn as id
2438
-
2439
- ### v4.5.3
2440
-
2441
- [Fixed]
2442
-
2443
- - plugin-api configuration file having new credentials for dummy-json testing
2444
-
2445
- [Improved]
2446
-
2447
- - Dependencies bump
2448
- - plugin-loki and plugin-mongodb, minor improvements for handling raw mulitivalue updates when not using default skipTypeConvert=false
2449
- - endpointMapper supporting comma separated string to be converted to array, e.g.:
2450
- SCIM otherMails = "myAlias1@company.com,myAlias2@company.com,myAlias3@company.com"
2451
-
2452
- endpointMapper configuration for endpoint attribute emails of type array:
2453
-
2454
- "map": {
2455
- "user": {
2456
- "emails": {
2457
- "mapTo": "otherMails",
2458
- "type": "array",
2459
- "typeInbound": "string"
2460
- },
2461
- ...
2462
-
2463
- ### v4.5.1
2464
-
2465
- [Improved]
2466
-
2467
- - scim-stream, client reconnect improvements
2468
-
2469
- ### v4.5.0
2470
-
2471
- [Improved]
2472
-
2473
- - scim-stream, scimgateway now supports stream publishing mode having [SCIM Stream](https://elshaug.xyz/docs/scim-stream) as a prerequisite. In this mode, standard incoming SCIM requests from your Identity Provider (IdP) or API are directed and published to the stream. Subsequently, one of the gateways subscribing to the channel utilized by the publisher will manage the SCIM request, and response back to the publisher. Using SCIM Stream we have `egress/outbound only traffic` and get loadbalancing/failover by adding more gateways subscribing to same channel.
2474
- - scim-stream, subscriber will do automatic retry until connected when plugin not able to connect to endpoint (offline endpoint)
2475
- - plugin-ldap, modifyGroup now supports all attributes and not only add/remove members
2476
- - certificate absolute path may be used in plugin configuration file instead of default relative path
2477
- - dependencies bump
2478
-
2479
- ### v4.4.6
2480
-
2481
- [Improved]
2482
-
2483
- - Some PUT logic redesign. More granularity on mulitvalues, instead of including all elements, now only those that differ are sent to modifyUser.
2484
-
2485
- ### v4.4.5
2486
-
2487
- [Fixed]
2488
-
2489
- - PATCH group members=[] should remove all members
2490
- - scim-stream modify user fix
2491
-
2492
- [Improved]
2493
-
2494
- - plugin-entra-id, plugin-scim and plugin-api having updated `REST endpoint helpers-template` that includes `tokenAuth` (now used by plugin-api). Auth PassTrhough also supported for oauth/tokenAuth endpoint
2495
- - PUT improvements
2496
-
2497
- ### v4.4.4
2498
-
2499
- [Improved]
2500
-
2501
- - New configuration: **scim.skipMetaLocation**
2502
- 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.
2503
-
2504
- Below is an example of nginx reverse proxy configuration supporting SCIM Gateway ipAllowList and correct meta.location response:
2505
-
2506
- proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
2507
- proxy_set_header X-Forwarded-Proto $scheme;
2508
- proxy_set_header X-Forwarded-Host $http_host;
2509
-
2510
- ### v4.4.3
2511
-
2512
- [Improved]
2513
-
2514
- - Dependencies bump
2515
-
2516
- ### v4.4.2
2517
-
2518
- [Improved]
2519
-
2520
- - scim-stream subscriber configuration have been changed:
2521
- old: `"convertRolesToGroups": false`
2522
- new: `"skipConvertRolesToGroups": false`
2523
- This means convert roles to groups is default behavior unless skipConvertRolesToGroups=true
2524
-
2525
- ### v4.4.1
2526
-
2527
- [Improved]
2528
-
2529
- - scim-stream subscriber using latest api and some additional recovery logic
2530
- Prerequisite: [SCIM Stream](https://elshaug.xyz/docs/scim-stream) version > v1.0.0
2531
-
2532
- [Fixed]
2533
-
2534
- - plugin-loki was missing async await and could cause problems in some stress test use cases
2535
-
2536
- ### v4.4.0
2537
-
2538
- [Improved]
2539
-
2540
- - SCIM Gateway now offers enhanced functionality with support for message subscription and automated provisioning using [SCIM Stream](https://elshaug.xyz/docs/scim-stream)
2541
- - plugin-entra-id, plugin-scim and plugin-api having updated `REST endpoint helpers-template` to address and resolve endpoint throttling
2542
-
2543
- Note, module soap is not default included anymore. SOAP based plugins e.g., plugin-soap therefore needs `npm install soap` for including module in your package
2544
-
2545
- ### v4.3.0
2546
-
2547
- [Improved]
2548
-
2549
- - configuration `scimgateway.scim.port` can now be set to 0 or removed for deactivating listener
2550
- - configuration `cimgateway.scim.usePutSoftSync` set to `true` now includes additional logic that do not change existing user attributes not included in PUT body content
2551
- - createUser/createGroup no longer return id if id have not been returned by plugin or by getUser filtering on userName. Previously userName was returned as id when missing plugin logic.
2552
- - plugin-ldap supporting simpel filtering
2553
- - plugin-loki using baseEntity configuration for supporting multi loki endpoints
2554
- - plugin-azure-ad renamed to plugin-entra-id
2555
- - plugin-entra-id and plugin-scim now using an updated default REST helpers-template that gives more flexible endpoint authentication support like OAuth, Basic, Bearer, custom-headers, no-auth,...
2556
- - Dependencies bump
2557
-
2558
- ### v4.2.17
2559
-
2560
- [Fixed]
2561
-
2562
- - plugin-loki incorrect unique filtering
2563
-
2564
- [Improved]
2565
-
2566
- - Dependencies bump
2567
-
2568
- ### v4.2.15
2569
-
2570
- [Improved]
2571
-
2572
- - Plugin can set error statusCode returned by scimgateway through error object key `err.name`. This can be done by adding suffix `#code` to err.name where code is HTTP status code e.g., `err.name += '#401'`. This can be useful for auth.PassThrough and other scenarios like createUser where user already exist (409) and modifyUser where user does not exist (404)
2573
-
2574
- This change replace statusCode logic introduced in v4.2.11
2575
-
2576
- ### v4.2.14
2577
-
2578
- [Fixed]
2579
-
2580
- - PUT now returning 404 instead of 500 when trying to update a user/group that does not exist
2581
-
2582
- ### v4.2.13
2583
-
2584
- [Fixed]
2585
-
2586
- - `/ping` now excluded from info logs. If we want ping logging, use something else than lowercase e.g., `/Ping` or `/PING`
2587
-
2588
- ### v4.2.12
2589
-
2590
- [Improved]
2591
-
2592
- - Schemas, ServiceProviderConfig and ResourceType can be customized if `lib/scimdef-v2.js (or scimdef-v1.js)` exists. Original scimdef-v2.js/scimdef-v1.js can be copied from node_modules/scimgateway/lib to your plugin/lib and customized.
2593
-
2594
- ### v4.2.11
2595
-
2596
- [Improved]
2597
-
2598
- Note, obsolete - see v4.2.15 comments
2599
-
2600
- - Plugin can set error statusCode returned by scimgateway through error message. Error message must then contain string `"statusCode":xxx` where xxx is HTTP status code e.g., 401. Plugin using REST will have statusCode automatically included in error message thrown by plugin. This could be useful for auth.PassThrough.
2601
-
2602
- ### v4.2.10
2603
-
2604
- [Fixed]
2605
-
2606
- - plugin-ldap broken after dependencies bump of ldapjs (from 2.x.x to 3.x.x) in version 4.2.7
2607
-
2608
- ### v4.2.9
2609
-
2610
- [Fixed]
2611
-
2612
- - installation require nodejs >= v.16.0.0 due to previous dependencies bump
2613
-
2614
- ### v4.2.8
2615
-
2616
- [Fixed]
2617
-
2618
- - PUT did not allow group name to be modified
2619
-
2620
- ### v4.2.7
2621
-
2622
- [Improved]
2623
-
2624
- - new plugin configuration **scim.usePutGroupMemberOfUser** can be set to true or false, default false. `PUT /Users/<user>` will replace user with body content. If body contains groups and usePutGroupMemberOfUser=true, groups will be set on user object (groups are member of user) instead of default user member of groups
2625
- - plugin-forwardinc renamed to plugin-soap
2626
- - Dependencies bump
2627
-
2628
- [Fixed]
2629
-
2630
- - plugin-azure-ad fixed some issues introduced in v4.2.4
2631
- - plugin-mongodb fixed some issues introduced in v4.2.4
2632
-
2633
- ### v4.2.6
2634
-
2635
- [Fixed]
2636
-
2637
- - cosmetics related to 401 error handling introduced in v4.2.4
2638
-
2639
- ### v4.2.5
2640
-
2641
- [Fixed]
2642
-
2643
- - travis test build cosmetics
2644
-
2645
- ### v4.2.4
2646
-
2647
- [Improved]
2648
-
2649
- - provided plugins now supports Auth PassThrough. See helpers methods like getClientIdentifier(), getCtxAuth() and changes in doRequest() and getServiceClient(). In general, PassThrough is supported for both basic and bearer auth. Password/secret/client_secret are then not needed in configuration file. Username may still be needed in configuration file depended on how logic is implemented (ref. mongodb/mssql) and what auth beeing used (basic/bearer). Plugin scim, api and azure-ad are all REST plugins having the same helpers (but, some minor differences to azure-ad using OAuth and the getAccessToken() method)
2650
-
2651
- ### v4.2.3
2652
-
2653
- [Fixed]
2654
-
2655
- - plugin-loki and plugin-mongodb, for multi-value attributes like emails,phoneNumbers,... that includes primary attribute, only one is allowed having primary value set to true in the multi-value set.
2656
-
2657
- ### v4.2.2
2658
-
2659
- [Fixed]
2660
-
2661
- - some minor SCIM protocol complient adjustments for beeing fully SCIM API complient with [https://scimvalidator.microsoft.com](https://scimvalidator.microsoft.com)
2662
-
2663
- ### v4.2.1
2664
-
2665
- [Fixed]
2666
-
2667
- - plugin-azure-ad createUser failed when manager was included
2668
- - plugin-ldap slow when not using group/groupBase configuration
2669
-
2670
-
2671
- ### v4.2.0
2672
-
2673
- [Improved]
2674
-
2675
- - Kubernetes health checks and shutdown handler support
2676
-
2677
- Plugin configuration prerequisite: **kubernetes.enabled=true**
2678
-
2679
- "kubernetes": {
2680
- "enabled": true,
2681
- "shutdownTimeout": 15000,
2682
- "forceExitTimeout": 1000
2683
- }
2684
-
2685
- **Thanks to [@Kevin Osborn](https://github.com/osbornk)**
2686
-
2687
- ### v4.1.15
2688
-
2689
- [Improved]
2690
-
2691
- - Authentication PassThrough for passing the authentication directly to plugin without being processed by scimgateway. Plugin can then pass this authentication to endpoint for avoid maintaining secrets at the gateway.
2692
-
2693
- Plugin configuration prerequisites: **auth.passThrough.enabled=true**
2694
-
2695
- "auth": {
2696
- ...
2697
- "passThrough": {
2698
- "enabled": true,
2699
- "readOnly": false,
2700
- "baseEntities": []
2701
- }
2702
- ...
2703
- }
2704
-
2705
- Plugin binary prerequisites:
2706
-
2707
- scimgateway.authPassThroughAllowed = true
2708
- // also need endpoint logic for handling/passing ctx.request.header.authorization
2709
-
2710
-
2711
- For upgrading existing custom plugins, above mention prerequisites needs to be included and in addition all plugin methods must include the `ctx` parameter e.g.:
2712
-
2713
- scimgateway.getUsers = async (baseEntity, getObj, attributes, ctx)
2714
- // tip, see provided example plugins
2715
-
2716
- **Thanks to [@Kevin Osborn](https://github.com/osbornk)**
2717
-
2718
- ### v4.1.14
2719
-
2720
- [Fixed]
2721
-
2722
- - Do not create logs directory or log-file when configuration `log.loglevel.file` not defined or set to `"off"`. This fix will allow SCIM Gateway to run on systems having read-only disk like Google Cloud App Engine Standard
2723
-
2724
- ### v4.1.12
2725
-
2726
- [Improved]
2727
-
2728
- - Dependencies bump
2729
-
2730
- ### v4.1.11
2731
-
2732
- [Fixed]
2733
-
2734
- - basic auth logon dialog should not show up when not configured
2735
-
2736
- ### v4.1.10
2737
-
2738
- [Improved]
2739
-
2740
- - new plugin configuration `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 e.g. "5mb"
2741
- **Thanks to [@Sam Murphy*](https://github.com/SamMurphyDev)**
2742
-
2743
- [Fixed]
2744
-
2745
- - using `GET /Users`, scimgateway automatically adds groups if not included by plugin. This operation calls plugin getGroups having attributes=['members.value', 'id', 'displayName']. Now, `members.value` is excluded. This attribute was in use and could cause unneeded load when having many group members.
2746
-
2747
- ### v4.1.9
2748
-
2749
- [Fixed]
2750
-
2751
- - plugin-azure-ad.json configuration file introduced in v.4.1.7 was missing passwordProfile attribute mappings
2752
- - Symantec/Broadcom/CA ConnectorXpress configuration file `config\resources\Azure - ScimGateway.xml` now using standard text on manager attribute instead of selection dialogbox.
2753
-
2754
- ### v4.1.8
2755
-
2756
- [Fixed]
2757
-
2758
- - endpointMap and Symantec/Broadcom/CA ConnectorXpress configuration file `config\resources\Azure - ScimGateway.xml` introduced in v.4.1.7 had some missing logic
2759
-
2760
- ### v4.1.7
2761
-
2762
- **Note, this version breaks compability with previous versions of plugin-azure-ad**
2763
-
2764
- [Improved]
2765
-
2766
- - endpointMap moved from scimgateway to plugin-azure-ad
2767
- - plugin-azure-ad.json configuration file now includes attribute mapping giving flexibility to add or customize AAD-SCIM attribute mappings
2768
- - Symantec/Broadcom/CA ConnectorXpress configuration file `config\resources\Azure - ScimGateway.xml` for defining the Azure endpoint, have been updated with some new attributes according to plugin-azure-ad.json attribute mappings
2769
-
2770
- ### v4.1.6
2771
-
2772
- [Improved]
2773
-
2774
- - Dependencies bump
2775
-
2776
- ### v4.1.5
2777
-
2778
- [Improved]
2779
-
2780
- SCIM Gateway related news:
2781
-
2782
- - [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 Stream includes **SCIM Stream Gateway**, the next generation SCIM Gateway that supports message subscription and automated provisioning
2783
-
2784
-
2785
- ### v4.1.4
2786
- [Fixed]
2787
-
2788
- - TypeConvert logic for multivalue attribute `addresses` did not correctly catch duplicate entries
2789
- - PUT (Replace User) configuration `scim.usePutSoftsync=true` will also prevent removing any existing roles that are not included in body.roles ref. v4.1.3
2790
-
2791
- ### v4.1.3
2792
- [Fixed]
2793
-
2794
- - createUser response did not include the id that was returned by plugin
2795
-
2796
- [Improved]
2797
-
2798
- - PUT (Replace User) now includes group handling. Using configuration `scim.usePutSoftsync=true` will prevent removing any existing groups that are not included in body.groups
2799
-
2800
- Example:
2801
-
2802
- PUT /Users/bjensen
2803
- {
2804
- ...
2805
- "groups": [
2806
- {"value":"Employees","display":"Employees"},
2807
- {"value":"Admins","display":"Admins"}
2808
- ],
2809
- ...
2810
- }
2811
-
2812
-
2813
-
2814
-
2815
- ### v4.1.2
2816
- [Improved]
2817
-
2818
- - endpointMapper supporting one to many mappings using a comma separated list of attributes in the `mapTo`
2819
-
2820
- Configuration example:
2821
-
2822
- "map": {
2823
- "user": {
2824
- "PersonnelNumber": {
2825
- "mapTo": "id,userName",
2826
- "type": "string"
2827
- },
2828
- ...
2829
- }
2830
- }
2831
-
2832
-
2833
- ### v4.1.1
2834
- [Improved]
2835
-
2836
- - plugin-ldap support userFilter/groupFilter configuration for restricting scope
2837
-
2838
- Configuration example:
2839
-
2840
- {
2841
- ...
2842
- "userFilter": "(memberOf=CN=grp1,OU=Groups,DC=test,DC=com)(!(memberOf=CN=Domain Admins,CN=Users,DC=test,DC=com))",
2843
- "groupFilter": "(!(cn=grp2))",
2844
- ...
2845
- }
2846
-
2847
- ### v4.1.0
2848
- [Improved]
2849
-
2850
- - Supporting OAuth Client Credentials authentication
2851
-
2852
- Configuration example:
2853
-
2854
- "bearerOAuth": [
2855
- {
2856
- "client_id": "my_client_id",
2857
- "client_secret": "my_client_secret",
2858
- "readOnly": false,
2859
- "baseEntities": []
2860
- }
2861
- ]
147
+ Install [Bun](https://bun.sh/) first. By default Bun installs to `HOMEPATH\.bun`. To install elsewhere, set `BUN_INSTALL=<path>` as a system environment variable before running the installer. Consider adding Bun to the system path for all users.
2862
148
 
149
+ ### Install SCIM Gateway
2863
150
 
2864
- In example above, client using SCIM Gateway must have OAuth configuration:
151
+ ```sh
152
+ mkdir c:\my-scimgateway
153
+ cd c:\my-scimgateway
154
+ bun init -y
155
+ bun install scimgateway
156
+ bun pm trust scimgateway # required to allow postinstall to copy example files
157
+ ```
2865
158
 
2866
- client_id = my_client_id
2867
- client_secret = my_client_secret
2868
- token request url = http(s)://<host>:<port>/oauth/token
159
+ This copies `index.ts`, `lib/`, and `config/` (with example plugins) into your package directory.
2869
160
 
161
+ ### Verify the Default Loki Plugin
2870
162
 
2871
- ### v4.0.1
2872
- [Improved]
163
+ ```sh
164
+ bun c:\my-scimgateway
165
+ ```
2873
166
 
2874
- - create user/group supporting externalId
2875
- - plugin-restful renamed to plugin-scim
2876
- - plugin-ldap having improved SID/GUID support for Active Directory, also supporting domain map of userPrincipalName e.g. Azure AD => Active Directory
2877
-
2878
- "userPrincipalName": {
2879
- "mapTo": "userName",
2880
- "type": "string",
2881
- "mapDomain": {
2882
- "inbound": "test.onmicrosoft.com",
2883
- "outbound": "my-company.com"
2884
- }
167
+ Then open a browser and try:
2885
168
 
2886
- - postinstall copying example plugins may be skipped by setting the property `scimgateway_postinstall_skip = true` in `.npmrc` or by setting environment `SCIMGATEWAY_POSTINSTALL_SKIP = true`
2887
- - Secrets now also support key-value storage. The key defined in plugin configuration have syntax `process.text.<path>` where `<path>` is the file which contains raw (UTF-8) character value. E.g. configuration `endpoint.password` could have value `process.text./var/run/vault/endpoint.password`, and the corresponding file contains the secret. **Thanks to [@Raymond Augé](https://github.com/rotty3000)**
169
+ ```
170
+ # Health check
171
+ GET http://localhost:8880/ping
2888
172
 
173
+ # List users and groups (basic auth: gwadmin / password)
174
+ GET http://localhost:8880/Users
175
+ GET http://localhost:8880/Groups
2889
176
 
2890
- ### v4.0.0
2891
- **[MAJOR]**
2892
-
2893
- - New `getUsers()` replacing deprecated exploreUsers(), getUser() and getGroupUsers()
2894
- - New `getGroups()` replacing deprecated exploreGroups(), getGroup() and getGroupMembers()
2895
- - Fully filter and sort support
2896
- - Authentication configuration may now include a baseEntities array containing one or more `baseEntity` allowed for corresponding admin user
2897
- - New plugin-mongodb, **Thanks to [@Filipe Ribeiro](https://github.com/fribeiro-keeps) and [@Miguel Ferreira](https://github.com/jmaferreira) (KEEP SOLUTIONS)**
177
+ # Real-time remote log monitoring
178
+ http://localhost:8880/logger
2898
179
 
2899
- Note, using this major version **require existing custom plugins to be upgraded**. If you do not want to upgrade your custom plugins, the old version have to be installed using: `npm install scimgateway@3.2.11`
180
+ # Fetch a specific user or group
181
+ GET http://localhost:8880/Users/bjensen
182
+ GET http://localhost:8880/Groups/Admins
2900
183
 
2901
- How to upgrade your custom plugins:
184
+ # Filter examples
185
+ GET http://localhost:8880/Users?filter=userName eq "bjensen"
186
+ GET http://localhost:8880/Users?filter=emails.value co "@example.com"&attributes=userName,name.familyName,emails&sortBy=name.familyName&sortOrder=descending
187
+ GET http://localhost:8880/Groups?filter=displayName eq "Admins"&excludedAttributes=members
188
+ GET http://localhost:8880/Groups?filter=members.value eq "bjensen"&attributes=id,displayName,members.value
189
+ ```
2902
190
 
2903
- Replace: scimgateway.exploreUsers = async (baseEntity, attributes, startIndex, count) => {
2904
- With: scimgateway.getUsers = async (baseEntity, getObj, attributes) => {
191
+ Press `Ctrl+C` to stop.
2905
192
 
2906
- See comments in provided plugins regarding the new `getObj`. Also note that `attributes` is now an array and not a comma separated string like previous versions
193
+ > Using **Node.js**, the startup command is: `node --import=tsx ./index.ts`
2907
194
 
2908
- In the very beginning, add:
195
+ ### Upgrading
2909
196
 
2910
- // mandatory if-else logic - start
2911
- if (getObj.operator) {
2912
- if (getObj.operator === 'eq' && ['id', 'userName', 'externalId'].includes(getObj.attribute)) {
2913
- // mandatory - unique filtering - single unique user to be returned - correspond to getUser() in versions < 4.x.x
2914
- } else if (getObj.operator === 'eq' && getObj.attribute === 'group.value') {
2915
- // optional - only used when groups are member of users, not default behavior - correspond to getGroupUsers() in versions < 4.x.x
2916
- throw new Error(`${action} error: not supporting groups member of user filtering: ${getObj.rawFilter}`)
2917
- } else {
2918
- // optional - simpel filtering
2919
- throw new Error(`${action} error: not supporting simpel filtering: ${getObj.rawFilter}`)
2920
- }
2921
- } else if (getObj.rawFilter) {
2922
- // optional - advanced filtering having and/or/not - use getObj.rawFilter
2923
- throw new Error(`${action} error: not supporting advanced filtering: ${getObj.rawFilter}`)
2924
- } else {
2925
- // mandatory - no filtering (!getObj.operator && !getObj.rawFilter) - all users to be returned - correspond to exploreUsers() in versions < 4.x.x
2926
- }
2927
- // mandatory if-else logic - end
197
+ The recommended approach is to rename the old package folder, do a fresh install, then copy your customized `index.ts`, `config/`, and `lib/` from the previous install.
2928
198
 
199
+ ```sh
200
+ # Minor upgrade
201
+ bun install scimgateway
2929
202
 
2930
- In the new getUsers() replacing exploreUsers() "as-is", we then need some logic in the last "else" statement listed above.
2931
- We also need to add logic from existing getGroup() and getGroupMembers()
2932
- **Please have a look at provieded plugins to see different ways of doing this logic.**
203
+ # Major upgrade (may break existing plugins review change log first)
204
+ bun install scimgateway@latest
205
+ ```
2933
206
 
207
+ **Excluding example plugins in production:** Bun skips `postinstall` unless you run `bun pm trust scimgateway`. For npm or Node.js environments, set `scimgateway_postinstall_skip = true` in `.npmrc` or the environment variable `SCIMGATEWAY_POSTINSTALL_SKIP=true`.
2934
208
 
2935
- Replace: scimgateway.exploreGroups = async (baseEntity, attributes, startIndex, count) => {
2936
- With: scimgateway.getGroups = async (baseEntity, getObj, attributes) => {
209
+ ---
2937
210
 
2938
- In the very beginning, add:
211
+ ## Configuration
2939
212
 
2940
- // mandatory if-else logic - start
2941
- if (getObj.operator) {
2942
- if (getObj.operator === 'eq' && ['id', 'displayName', 'externalId'].includes(getObj.attribute)) {
2943
- // mandatory - unique filtering - single unique user to be returned - correspond to getUser() in versions < 4.x.x
2944
- } else if (getObj.operator === 'eq' && getObj.attribute === 'members.value') {
2945
- // mandatory - return all groups the user 'id' (getObj.value) is member of - correspond to getGroupMembers() in versions < 4.x.x
2946
- // Resources = [{ id: <id-group>> , displayName: <displayName-group>, members [{value: <id-user>}] }]
2947
- } else {
2948
- // optional - simpel filtering
2949
- throw new Error(`${action} error: not supporting simpel filtering: ${getObj.rawFilter}`)
2950
- }
2951
- } else if (getObj.rawFilter) {
2952
- // optional - advanced filtering having and/or/not - use getObj.rawFilter
2953
- throw new Error(`${action} error: not supporting advanced filtering: ${getObj.rawFilter}`)
2954
- } else {
2955
- // mandatory - no filtering (!getObj.operator && !getObj.rawFilter) - all groups to be returned - correspond to exploreGroups() in versions < 4.x.x
2956
- }
2957
- // mandatory if-else logic - end
213
+ ### Entry Point `index.ts`
2958
214
 
215
+ `index.ts` defines which plugins to start:
2959
216
 
2960
- In the new getGroups() replacing exploreGroups() "as-is", we then need some logic in the last "else" statement listed above.
2961
- We also need to add logic from existing getGroup() and getGroupMembers()
2962
- **Please have a look at provieded plugins to see different ways of doing this logic.**
217
+ ```ts
218
+ // Start one or more plugins:
219
+ import './lib/plugin-entra-id.ts'
220
+ export {}
221
+ ```
2963
222
 
223
+ ### Plugin File Naming
2964
224
 
2965
- Delete deprecated exploreUsers(), getUser(), getGroupUsers(), exploreGroups(), getGroup() and getGroupMembers()
225
+ Each plugin requires a TypeScript file and a JSON configuration file sharing the same name prefix:
2966
226
 
227
+ ```
228
+ lib/plugin-entra-id.ts
229
+ config/plugin-entra-id.json
230
+ ```
2967
231
 
2968
- ### v3.2.11
2969
- [Fixed]
232
+ The JSON file has two top-level objects:
2970
233
 
2971
- - errorhandling related to running scimgateway as unikernel
234
+ ```json
235
+ {
236
+ "scimgateway": { ... },
237
+ "endpoint": { ... }
238
+ }
239
+ ```
2972
240
 
2973
- ### v3.2.10
2974
- [Fixed]
241
+ `scimgateway` holds gateway core settings (port, auth, logging, TLS). `endpoint` holds plugin-specific connection details (host, credentials, mappings).
2975
242
 
2976
- - for SCIM 2.0 exploreUsers/exploreGroups now includes schemas/resourceType on each object in the Resources response. This may be required by som IdP's.
243
+ ---
2977
244
 
2978
- [Improved]
2979
- - Dependencies bump
245
+ ### Core Options
246
+
247
+ | Option | Type | Default | Description |
248
+ |---|---|---|---|
249
+ | `port` | number | — | Port the gateway listens on |
250
+ | `localhostonly` | boolean | false | Accept requests only from `127.0.0.1` |
251
+ | `chainingBaseUrl` | string | — | Route requests to another gateway (`http(s)://host:port`) |
252
+ | `idleTimeout` | number | 120 | Seconds before an idle connection is dropped |
253
+ | `scim.version` | string | `"2.0"` | SCIM protocol version: `"1.1"` or `"2.0"` |
254
+ | `scim.skipTypeConvert` | boolean | false | Pass multivalue attributes as-is instead of type-converted objects |
255
+ | `scim.skipMetaLocation` | boolean | false | Omit `meta.location` from responses (useful behind a reverse proxy) |
256
+ | `scim.groupMemberOfUser` | boolean | false | Keep `groups` on the user object instead of managing group membership via `modifyGroup` |
257
+ | `scim.usePutSoftSync` | boolean | false | `PUT` replaces only the attributes in the body; existing attributes are preserved |
258
+
259
+ **Logging options (`log.*`):**
260
+
261
+ | Option | Values | Default | Description |
262
+ |---|---|---|---|
263
+ | `log.loglevel.file` | off, debug, info, warn, error | off | Log level for the plugin log file |
264
+ | `log.loglevel.console` | off, debug, info, warn, error | off | Log level for stdout/stderr |
265
+ | `log.loglevel.push` | debug, info, warn, error | info | Log level for the remote real-time subscriber |
266
+ | `log.logDirectory` | path | `<package>/logs` | Override the default log directory |
267
+ | `log.customMasking` | string[] | `[]` | Additional attribute names to mask in logs, e.g. `["SSN", "weight"]` |
268
+ | `log.colorize` | boolean | true | Colorized console output; set false for plain JSON |
269
+ | `log.maxSize` | number | 20 | Max log file size in MB |
270
+ | `log.maxFiles` | number | 5 | Number of rotated log files to keep |
271
+
272
+ **`scim.skipTypeConvert` example:**
273
+
274
+ With `skipTypeConvert: false` (default), emails are converted to type-keyed objects:
275
+
276
+ ```json
277
+ "emails": {
278
+ "work": { "value": "jsmith@example.com", "type": "work" },
279
+ "home": { "value": "", "type": "home", "operation": "delete" }
280
+ }
281
+ ```
2980
282
 
2981
- ### v3.2.9
2982
- [Fixed]
283
+ With `skipTypeConvert: true`, the array is passed as-is:
2983
284
 
2984
- - plugin-loki pagination fix
285
+ ```json
286
+ "emails": [
287
+ { "value": "jsmith@example.com", "type": "work" },
288
+ { "value": "john.smith.org", "type": "home", "operation": "delete" }
289
+ ]
290
+ ```
2985
291
 
2986
- ### v3.2.8
2987
- [Fixed]
292
+ ---
2988
293
 
2989
- - plugin-ldap `objectGUID` introduced in v.3.2.7 had some missing logic
294
+ ### Authentication
2990
295
 
2991
- ### v3.2.7
2992
- [Improved]
296
+ The `auth` object supports multiple concurrent methods. Set any admin user to `null` to disable that method. Each entry supports:
2993
297
 
2994
- - plugin-ldap supports using Active Directory `objectGUID` instead of `dn` mapped to `id`
2995
- configuration example:
2996
-
2997
- "objectGUID": {
2998
- "mapTo": "id",
2999
- "type": "string"
3000
- }
298
+ - `readOnly` if `true`, only `GET` requests are allowed
299
+ - `baseEntities` — restrict this credential to specific baseEntity values (empty array = all)
3001
300
 
3002
- [Fixed]
301
+ #### Basic Authentication
3003
302
 
3004
- - Return 500 on GET handler error instead of 404
3005
- **Thanks to [@Nipun Dayanath](https://github.com/nipund)**
3006
- - createUser/createRole response now includes id retrieved by getUser/getRole instead of using posted userName/displayName value
303
+ ```json
304
+ "auth": {
305
+ "basic": [
306
+ {
307
+ "username": "gwadmin",
308
+ "password": "password",
309
+ "readOnly": false,
310
+ "baseEntities": []
311
+ }
312
+ ]
313
+ }
314
+ ```
3007
315
 
3008
- ### v3.2.6
3009
- [Fixed]
316
+ Cleartext passwords are encrypted on first gateway start.
3010
317
 
3011
- - bearerJwt authentication missing public key handling
3012
- - plugin-azure-ad getGroup did not return all members when group had more than 100 members (Azure page size is 100). getGroup now using paging
318
+ #### Bearer Token (Shared Secret)
3013
319
 
3014
- ### v3.2.5
3015
- [Fixed]
320
+ ```json
321
+ "bearerToken": [
322
+ {
323
+ "token": "my-shared-secret",
324
+ "readOnly": false,
325
+ "baseEntities": []
326
+ }
327
+ ]
328
+ ```
3016
329
 
3017
- - default "type converted object" logic may fail on requests that includes a mix of type and blank type. Now blank type will be converted to type "undefined", and all types must be unique within the same request. "type converted object" logic can be turned off by configuration `scim.skipTypeConvert = true`
3018
- - plugin-loki supporting type = "undefined"
330
+ Supported by Entra ID provisioning. The token is encrypted on first start.
3019
331
 
3020
- [Improved]
332
+ #### JWT (Standard)
3021
333
 
3022
- - new configuration `scim.skipTypeConvert` allowing overriding the default behaviour "type converted object" when set to true. See attribute list for details
3023
- - `scimgateway.isMultivalue` used by plugin-loki have been changed, and **custom plugins using this method must be updated**
3024
-
3025
- old syntax:
3026
- scimgateway.isMultivalue('User', key)
334
+ ```json
335
+ "bearerJwt": [
336
+ {
337
+ "secret": null,
338
+ "publicKey": "jwt-public-key.pem",
339
+ "wellKnownUri": null,
340
+ "azureTenantId": null,
341
+ "options": {
342
+ "issuer": "https://my-idp.example.com"
343
+ },
344
+ "readOnly": false,
345
+ "baseEntities": []
346
+ }
347
+ ]
348
+ ```
3027
349
 
3028
- new syntax:
3029
- scimgateway.isMultiValueTypes(key)
350
+ - `secret` — HMAC shared secret (encrypted on start)
351
+ - `publicKey` — filename of a PEM file in `config/certs/`
352
+ - `wellKnownUri` — JWKS discovery URL, e.g. `https://keycloak.example.com/realms/my-realm/.well-known/openid-configuration`
353
+ - `azureTenantId` — Entra ID tenant ID; enables Entra-initiated provisioning using JWT validation
3030
354
 
3031
- ### v3.2.4
3032
- [Fixed]
355
+ For Entra ID apps accessing the gateway:
3033
356
 
3034
- - plugin-loki some code cleanup
357
+ ```json
358
+ "wellKnownUri": "https://login.microsoftonline.com/{tenant-id}/v2.0/.well-known/openid-configuration",
359
+ "options": { "audience": "{application-id}" }
360
+ ```
3035
361
 
3036
- ### v3.2.3
3037
- [Fixed]
362
+ #### OAuth Client Credentials
3038
363
 
3039
- - PUT was not according to the SCIM specification
3040
- - plugin-mssql broken after dependencies bump v3.1.0
3041
- - plugin-loki getUser using `find` instead of `findOne` to ensure returning unique user
364
+ ```json
365
+ "bearerOAuth": [
366
+ {
367
+ "clientId": "my-client-id",
368
+ "clientSecret": "my-client-secret",
369
+ "readOnly": false,
370
+ "baseEntities": []
371
+ }
372
+ ]
373
+ ```
3042
374
 
3043
- ### v3.2.2
3044
- [Fixed]
375
+ Clients request a token from `POST /oauth/token` (e.g. `http://localhost:8880/oauth/token`).
3045
376
 
3046
- - plugins missing logic for handling the virtual readOnly user attribute `groups` (when `"user member of groups"`) e.g. GET /Users/bjensen should return all user attributes including the virtual `groups` attribute. Now this user attribute will be automatically handled by scimgateway if not included in the plugin response.
3047
- - Pre and post actions onAddGroups/onRemoveGroups introduced in v.3.2.0 has been withdrawn
377
+ #### Authentication PassThrough
3048
378
 
3049
- [Improved]
379
+ ```json
380
+ "passThrough": {
381
+ "enabled": true,
382
+ "readOnly": false,
383
+ "baseEntities": []
384
+ }
385
+ ```
3050
386
 
3051
- - scimgateway will do plugin response filtering according to requested attributes/excludedAttributes
387
+ The gateway forwards the raw `Authorization` header to the plugin. The plugin must set `scimgateway.authPassThroughAllowed = true` and implement its own auth handling against the endpoint.
3052
388
 
389
+ ---
3053
390
 
3054
- ### v3.2.1
3055
- [Fixed]
391
+ ### IP Allow List
3056
392
 
3057
- - plugin-azure-ad updating businessPhones (Office phone) broken after v3.2.0
3058
- - plugin-azure-ad listing groups for user did also include Azure roles
3059
- - SCIM v2.0 none core schema attributes handling
3060
- - response not always including correct schemas
393
+ Restrict incoming traffic to specific subnets (CIDR notation). Useful for Entra ID provisioning where you want to accept traffic only from Azure IP ranges:
3061
394
 
3062
- [Improved]
395
+ ```json
396
+ "ipAllowList": [
397
+ "13.64.151.161/32",
398
+ "13.66.141.64/27",
399
+ "2603:1056:2000::/48"
400
+ ]
401
+ ```
3063
402
 
3064
- - roles now using array instead of objects based on type. **Note, this may break your custom plugins if roles logic are in use**
403
+ > Azure IP ranges can be downloaded from [azureipranges.azurewebsites.net](https://azureipranges.azurewebsites.net) search for `AzureActiveDirectory` and copy the `addressPrefixes` array.
3065
404
 
3066
- ### v3.2.0
3067
- [Improved]
405
+ When running behind a load balancer or reverse proxy, the proxy must include the client IP in the `X-Forwarded-For` header.
3068
406
 
3069
- - ipAllowList for restricting access to allowlisted IP addresses or subnets e.g. Azure AD IP-range
3070
- Configuration example:
3071
-
3072
- "ipAllowList": [
3073
- "13.66.60.119/32",
3074
- "13.66.143.220/30",
3075
- ...
3076
- "2603:1056:2000::/48",
3077
- "2603:1057:2::/48"
3078
- ]
407
+ ---
3079
408
 
3080
- - Example plugins now configured for SCIM v2.0 instead of v1.1
409
+ ### TLS & Certificates
3081
410
 
3082
- New configuration:
3083
-
3084
- "scim": {
3085
- "version": "2.0"
3086
- }
3087
-
3088
- Old configuration:
3089
-
3090
- "scim": {
3091
- "version": "1.1"
3092
- }
411
+ #### Using PEM files
3093
412
 
413
+ ```json
414
+ "certificate": {
415
+ "key": "key.pem",
416
+ "cert": "cert.pem",
417
+ "ca": "ca.pem"
418
+ }
419
+ ```
3094
420
 
3095
- ### v3.1.0
3096
- [Improved]
421
+ Files must be in `config/certs/` or use absolute paths. For multiple CAs: `"ca": ["ca1.pem", "ca2.pem"]`.
3097
422
 
3098
- - plugin-ldap a general LDAP plugin pre-configured for Microsoft Active Directory. Using endpointMapper logic (like plugin-azure-ad) for attribute flexibility
3099
- - Pre and post actions onAddGroups/onRemoveGroups can be configured and needed logic to be defined in plugin method `pre_post_Action`
3100
- - Dependencies bump
423
+ **Generate a self-signed certificate:**
3101
424
 
3102
- ### v3.0.8
3103
- [Fixed]
425
+ ```sh
426
+ openssl req -nodes -newkey rsa:2048 -x509 -sha256 -days 3650 \
427
+ -keyout key.pem -out cert.pem \
428
+ -subj "/O=My Company/OU=Application/CN=SCIM Gateway" \
429
+ -addext "subjectAltName=DNS:localhost,DNS:127.0.0.1,DNS:*.mycompany.com" \
430
+ -addext "extendedKeyUsage=serverAuth" \
431
+ -addext "keyUsage=digitalSignature"
432
+ ```
3104
433
 
3105
- - plugin-azure-ad delete account fails in v3.x
434
+ #### Using PFX / PKCS#12
3106
435
 
3107
- ### v3.0.7
3108
- [Fixed]
436
+ ```json
437
+ "pfx": {
438
+ "bundle": "certbundle.pfx",
439
+ "password": "password"
440
+ }
441
+ ```
3109
442
 
3110
- - Using proxy configuration broken in v3.x
443
+ > If communicating over localhost only (e.g. gateway installed directly on the provisioning server), you can skip TLS and use `http://localhost:<port>` with `"localhostonly": true`.
3111
444
 
3112
- ### v3.0.6
3113
- [Fixed]
445
+ #### No TLS
3114
446
 
3115
- - Dependencies bump
447
+ ```json
448
+ "certificate": {
449
+ "key": null,
450
+ "cert": null,
451
+ "ca": null
452
+ }
453
+ ```
3116
454
 
3117
- ### v3.0.4
3118
- [Improved]
455
+ ---
3119
456
 
3120
- - Pagination request having startIndex but no count, now sets count to default 200 and may be overridden by plugin.
457
+ ### Email Notifications
3121
458
 
3122
- ### v3.0.3
3123
- [Fixed]
459
+ The `email` section supports alerting on errors and sending mail from plugin code via `scimgateway.sendMail()`.
3124
460
 
3125
- - GET /Users?startIndex=1&count=100 with no attributes filter included did not work
461
+ #### Microsoft Exchange Online (OAuth)
3126
462
 
3127
- ### v3.0.2
3128
- [Fixed]
463
+ ```json
464
+ "email": {
465
+ "auth": {
466
+ "type": "oauth",
467
+ "options": {
468
+ "azureTenantId": "<tenant-id>",
469
+ "clientId": "<client-id>",
470
+ "clientSecret": "<client-secret>"
471
+ }
472
+ },
473
+ "emailOnError": {
474
+ "enabled": true,
475
+ "from": "noreply@example.com",
476
+ "to": "ops-team@example.com",
477
+ "cc": null,
478
+ "subject": "SCIM Gateway error",
479
+ "sendInterval": 15
480
+ }
481
+ }
482
+ ```
3129
483
 
3130
- - SCIM v2.0 PUT did not work.
484
+ **Entra ID requirements:**
485
+ 1. Grant the application permission `Mail.Send`
486
+ 2. Restrict which mailboxes the app can send from via an Exchange `ApplicationAccessPolicy`:
3131
487
 
3132
- ### v3.0.1
3133
- [Improved]
488
+ ```powershell
489
+ Install-Module -Name ExchangeOnlineManagement
490
+ Connect-ExchangeOnline
3134
491
 
3135
- - getApi supports body (apiObj).
492
+ New-ApplicationAccessPolicy `
493
+ -AppId <AppClientID> `
494
+ -PolicyScopeGroupId <MailEnabledSecurityGroupId> `
495
+ -AccessRight RestrictAccess `
496
+ -Description "Restrict app to specific mailboxes"
497
+ ```
3136
498
 
3137
- Old syntax:
3138
-
3139
- scimgateway.getApi = async (baseEntity, id, apiQuery) => {
3140
-
3141
- New syntax:
3142
-
3143
- scimgateway.getApi = async (baseEntity, id, apiQuery, apiObj) => {
499
+ #### Google Workspace Gmail (OAuth)
3144
500
 
501
+ ```json
502
+ "email": {
503
+ "auth": {
504
+ "type": "oauth",
505
+ "options": {
506
+ "serviceAccountKeyFile": "google-service-account.json"
507
+ }
508
+ },
509
+ "emailOnError": {
510
+ "enabled": true,
511
+ "from": "sender@example.com",
512
+ "to": "ops@example.com"
513
+ }
514
+ }
515
+ ```
3145
516
 
3146
- ### v3.0.0
3147
- **[MAJOR]**
517
+ **Google setup:**
518
+ 1. [Google Cloud Console](https://console.cloud.google.com): create a Service Account → download the JSON key
519
+ 2. [Google Admin](https://admin.google.com): Security → API controls → Domain Wide Delegation → add Client ID with scope `https://www.googleapis.com/auth/gmail.send`
520
+ 3. Ensure `from` address has a Google Workspace license
521
+
522
+ #### SMTP Auth
523
+
524
+ ```json
525
+ "email": {
526
+ "auth": {
527
+ "type": "smtp",
528
+ "options": {
529
+ "host": "smtp.gmail.com",
530
+ "port": 587,
531
+ "username": "user@gmail.com",
532
+ "password": "app-password"
533
+ }
534
+ },
535
+ "emailOnError": {
536
+ "enabled": true,
537
+ "to": "ops@example.com"
538
+ }
539
+ }
540
+ ```
3148
541
 
3149
- - getUser/getGroup now using parameter getObj giving more flexibility
3150
- - deprecated modifyGroupMembers - now using modifyGroup
3151
- - deprecated configuration `scimgateway.scim.customUniqueAttrMapping` - replaced by getObj logic
3152
- - loglevel=off turns of logging
3153
- - Auth methods allowing more than one user/object including option for readOnly
3154
- - Includes latest versions of module dependencies
3155
-
542
+ ---
3156
543
 
3157
- **[UPGRADE]**
544
+ ### Azure Relay
3158
545
 
3159
- Note, this is a major upgrade (^2.x.x => ^3.x.x) that will brake compatibility with any existing custom plugins. To force a major upgrade, suffix `@latest` must be include in the npm install command, but it's recommended to do a fresh install and copy any custom plugins instead of upgrading an existing package
546
+ Azure Relay lets the gateway listen for inbound SCIM requests over an outbound HTTPS/443 connection no inbound firewall rules required.
3160
547
 
3161
- Old syntax:
548
+ **Cost:** ~$10/month per Hybrid Connection listener.
3162
549
 
3163
- scimgateway.getUser = async (baseEntity, userName, attributes) => {
3164
- scimgateway.getGroup = async (baseEntity, displayName, attributes) => {
3165
- scimgateway.modifyGroupMembers = async (baseEntity, id, members) => {
550
+ **Azure setup:**
551
+ 1. Create a Relay namespace in Azure → create a Hybrid Connection entity (one per plugin)
552
+ 2. Leave **Requires Client Authorization** unchecked unless your IdP includes a SAS token
553
+ 3. Copy the primary key from Shared Access Policies → RootManageSharedaccessKey
3166
554
 
3167
- New syntax:
555
+ **Plugin configuration:**
3168
556
 
3169
- scimgateway.getUser = async (baseEntity, getObj, attributes) => {
3170
- const userName = getObj.identifier // gives v2.x compatibility
557
+ ```json
558
+ "azureRelay": {
559
+ "enabled": true,
560
+ "connectionUrl": "https://<namespace>.servicebus.windows.net/<hybrid-connection>",
561
+ "apiKey": "<primary-key>",
562
+ "keyRule": "RootManageSharedaccessKey"
563
+ }
564
+ ```
3171
565
 
3172
- scimgateway.getGroup = async (baseEntity, getObj, attributes) => {
3173
- const displayName = getObj.identifier // gives v2.x compatibility
566
+ The `connectionUrl` becomes the SCIM base URL. Examples:
3174
567
 
3175
- scimgateway.modifyGroup = async (baseEntity, id, attrObj) => {
3176
- // attrObj.members corresponds to members in deprecated modifyGroupMembers
568
+ ```
569
+ GET https://<namespace>.servicebus.windows.net/<hybrid-connection>/Users
570
+ GET https://<namespace>.servicebus.windows.net/<hybrid-connection>/<baseEntity>/Users
571
+ ```
3177
572
 
3178
- getUser comments:
3179
- getObj = `{ filter: <filterAttribute>, identifier: <identifier> }`
3180
- e.g: getObj = `{ filter: 'userName', identifier: 'bjensen'}`
3181
- filter: userName and id must be supported
573
+ Multiple gateway instances sharing the same `connectionUrl` will round-robin load-balance.
3182
574
 
3183
- getGroup comments:
3184
- getObj = `{ filter: <filterAttribute>, identifier: <identifier> }`
3185
- e.g: getObj = `{ filter: 'displayName', identifier: 'GroupA' }`
3186
- filter: displayName and id must be supported
575
+ > Azure Relay does not support remote log subscription.
3187
576
 
3188
- **Please see provided example plugins**
577
+ ---
3189
578
 
3190
- Using the new getObj parameter gives more flexibility in the way of lookup a user e.g:
3191
- `http://localhost:8880/Users?filter=emails.value eq "jsmith@example.com"&attributes=userName,name.givenName`
3192
- getObj = `{ filter: 'emails.value', identifier: 'jsmith@example.com'}`
3193
- attributes = `'userName,name.givenName'`
579
+ ### Secrets from External Sources
3194
580
 
3195
- Configuration file, auth settings have changed and now using arrays allowing more than one user/object to be set. `"readOnly": true` can also be set for allowing read only access for a spesific user (does not apply to bearerJwtAzure).
581
+ All configuration values can be sourced from environment variables, external JSON files, or plain text files. This supports secret managers and Kubernetes secrets.
3196
582
 
3197
- New syntax is:
583
+ **From environment variables:**
3198
584
 
3199
- "auth": {
3200
- "basic": [
3201
- {
3202
- "username": "gwadmin",
3203
- "password": "password",
3204
- "readOnly": false
3205
- }
3206
- ],
3207
- "bearerToken": [
3208
- {
3209
- "token": null,
3210
- "readOnly": false
3211
- }
3212
- ],
3213
- "bearerJwtAzure": [
3214
- {
3215
- "tenantIdGUID": null
3216
- }
3217
- ],
3218
- "bearerJwt": [
3219
- {
3220
- "secret": null,
3221
- "publicKey": null,
3222
- "options": {
3223
- "issuer": null
3224
- },
3225
- "readOnly": false
3226
- }
3227
- ]
3228
- }
585
+ ```json
586
+ "port": "process.env.PORT",
587
+ "log": { "loglevel": { "file": "process.env.LOG_LEVEL_FILE" } }
588
+ ```
3229
589
 
590
+ **From a shared JSON file** (dot-notation keyed by plugin name):
3230
591
 
3231
- ### v2.1.13
3232
- [Fixed]
592
+ ```json
593
+ "username": "process.file./var/run/vault/secrets.json"
594
+ ```
3233
595
 
3234
- - Plugin configuration referring to an external configuration file using an array did not work.
596
+ Where `secrets.json` contains:
3235
597
 
3236
- ### v2.1.11
3237
- [Fixed]
598
+ ```json
599
+ {
600
+ "plugin-soap.scimgateway.auth.basic[0].username": "gwadmin",
601
+ "plugin-soap.scimgateway.auth.basic[0].password": "password",
602
+ "plugin-soap.endpoint.username": "superuser",
603
+ "plugin-soap.endpoint.password": "secret"
604
+ }
605
+ ```
3238
606
 
3239
- - Log masking of xml (SOAP) messages.
607
+ **From a single-value text file:**
3240
608
 
609
+ ```json
610
+ "secret": "process.text./var/run/vault/jwt.secret"
611
+ ```
3241
612
 
3242
- ### v2.1.10
3243
- [Improved]
613
+ Where the file contains the raw value: `thisIsSecret`
3244
614
 
3245
- - Log masking of custom defined attributes.
3246
- customMasking may include an array of attributes to be masked
3247
- e.g. `"customMasking": ["SSN", "weight"]`
3248
- - Note, configurationfiles must be changed (old syntax still supported)
3249
- old syntax:
615
+ > Set the environment variable `SEED` to a random string to override default password seeding. This also lets you copy an encrypted configuration file between machines.
3250
616
 
3251
- "loglevel": {
3252
- "file": "debug",
3253
- "console": "error"
3254
- },
3255
- new syntax:
617
+ ---
3256
618
 
3257
- "log": {
3258
- "loglevel": {
3259
- "file": "debug",
3260
- "console": "error"
3261
- },
3262
- "customMasking": []
3263
- },
3264
- By default SCIM Gateway includes masking of standard attributes like password
619
+ ### Remote Log Subscription
3265
620
 
3266
- ### v2.1.9
3267
- [Fixed]
621
+ Stream real-time logs from the gateway to a browser, curl, or custom client.
3268
622
 
3269
- - AAD as IdP broken after content-type validation introduced in v2.1.7
3270
- - AAD as IdP, none gallery app support
3271
- - Incorrect SCIM 2.0 multivalue converting
3272
- - plugin-saphana not correctly ported to v2.x
623
+ **Browser:** `https://<host>/logger`
3273
624
 
3274
- **Thanks to Luca Moretto**
625
+ **curl:**
3275
626
 
3276
- ### v2.1.8
3277
- [Fixed]
627
+ ```sh
628
+ curl -Ns http://localhost:8880/logger -u gwadmin:password | awk '
629
+ /^data: / {sub(/^data: /,""); printf "%s", $0; last=1; next}
630
+ /^$/ {if (last) print ""; last=0}
631
+ '
632
+ ```
3278
633
 
3279
- - plugin-mssql not correctly ported to v2.x, and some config syntax for this plugin have also changed in newer releases of dependencies.
634
+ **Custom client (TypeScript/Bun):**
3280
635
 
3281
- ### v2.1.7
3282
- [Fixed]
636
+ ```ts
637
+ const username = "gwadmin"
638
+ const password = "password"
639
+ const url = "http://localhost:8880/logger"
3283
640
 
3284
- - Validates content-type when body is included
3285
- - Case insensitive log-masking
3286
- - Plugins now don't using deprecated `url.parse`
3287
- - Misc cosmetics e.g. using const instead of let when not reassigned
641
+ const headers = new Headers({
642
+ Authorization: "Basic " + btoa(`${username}:${password}`),
643
+ Accept: "text/event-stream"
644
+ })
3288
645
 
3289
- ### v2.1.6
3290
- [Fixed]
646
+ // message handling and custom logic
647
+ const messageHandler = async (message: string) => {
648
+ console.log(message)
649
+ }
3291
650
 
3292
- - plugin-azure-ad did not return correct error code (`err.name = 'DuplicateKeyError'`) when failing on creating a duplicate user
651
+ async function startup() {
652
+ while (true) {
653
+ try {
654
+ const resp = await fetch(url, { headers })
655
+ if (!resp.ok || !resp.body) {
656
+ console.error(`❌ Response error: ${resp.status} ${resp.statusText}`)
657
+ await Bun.sleep(10_000)
658
+ continue
659
+ }
660
+ console.log('✅ Connected — awaiting log events...\n')
661
+ const reader = resp.body.pipeThrough(new TextDecoderStream()).getReader()
662
+ while (true) {
663
+ const { value, done } = await reader.read()
664
+ if (done) break
665
+ if (!value.startsWith('data: ')) continue
666
+ const i = value.indexOf("\n\n")
667
+ if (i < 1) continue
668
+ messageHandler(value.slice(6, i))
669
+ }
670
+ console.error("⚠️ Connection closed")
671
+ await Bun.sleep(10_000)
672
+ } catch (err: any) {
673
+ console.error("❌ Connection error:", err?.message || err)
674
+ await Bun.sleep(10_000)
675
+ }
676
+ }
677
+ }
3293
678
 
3294
- [Improved]
679
+ startup()
680
+ ```
3295
681
 
3296
- - Includes latest versions of module dependencies
682
+ Set a dedicated read-only credential for log collection:
683
+
684
+ ```json
685
+ "auth": {
686
+ "basic": [
687
+ { "username": "gwadmin", "password": "password", "readOnly": false },
688
+ { "username": "gwread", "password": "password", "readOnly": true }
689
+ ],
690
+ "bearerToken": [
691
+ { "token": "log-secret", "readOnly": true }
692
+ ]
693
+ }
694
+ ```
3297
695
 
3298
- ### v2.1.4
3299
- [Fixed]
696
+ Set push log level (default `info`):
3300
697
 
3301
- - Incorrect SCIM 2.0 error handling after v2.1.0
3302
- - For duplicate key error, setting `err.name = 'DuplicateKeyError'` now gives correct status code 409 instead of defult 500 (see plugin-loki.js)
698
+ ```json
699
+ "log": { "loglevel": { "push": "debug" } }
700
+ ```
3303
701
 
3304
- ### v2.1.3
3305
- [Fixed]
702
+ You can also scope log output to a specific `baseEntity`: `https://<host>/<baseEntity>/logger`
3306
703
 
3307
- - Standardized the API Gateway response (not SCIM related)
3308
- - Not allowing plugins to return password
3309
- - Colorize option now automatically turned off when using stdout/stderr redirect (configuration file `loglevel.colorize` is not needed)
704
+ ---
3310
705
 
3311
- ### v2.1.2
3312
- [Fixed]
706
+ ### Gateway Chaining
3313
707
 
3314
- - SCIM 2.0 may use Operations.value as array and none array (issue #16)
708
+ Chain multiple gateways: `gateway1 gateway2 gateway3 → endpoint`. Each gateway validates authorization and forwards the request unless PassThrough is enabled.
3315
709
 
3316
- [Improved]
710
+ **gateway1 configuration:**
3317
711
 
3318
- - Option for replacing mandatory userName/displayName attribute by configuring customUniqueAttrMapping
3319
- - Includes latest versions of module dependencies
712
+ ```json
713
+ {
714
+ "scimgateway": {
715
+ "chainingBaseUrl": "https://gateway2:8880",
716
+ "auth": {
717
+ "passThrough": {
718
+ "enabled": false
719
+ }
720
+ }
721
+ }
722
+ }
723
+ ```
3320
724
 
3321
- ### v2.1.1
3322
- [Fixed]
725
+ In chaining mode the plugin binary is only used for initialization. You can simplify the plugin to the mandatory section only:
3323
726
 
3324
- - SCIM 2.0 may use Operations.value or Operation.value[] for PATCH syntax of the name object (issue #14)
3325
- - plugin-loki failed to modify a none existing object, e.g name object not included in Create User
727
+ ```ts
728
+ // start - mandatory plugin initialization
729
+ import { ScimGateway } from 'scimgateway'
730
+ const scimgateway = new ScimGateway()
731
+ const config = scimgateway.getConfig()
732
+ scimgateway.authPassThroughAllowed = true // and configuration file having: scimgateway.auth.passThrough=true
733
+ scimgateway.pluginAndOrFilterEnabled = false
734
+ // end - mandatory plugin initialization
735
+ ```
3326
736
 
3327
- ### v2.1.0
3328
- [Improved]
737
+ ---
3329
738
 
3330
- - Custom schema attributes can be added by plugin configuration `scim.customSchema` having value set to filename of a JSON schema-file located in `<package-root>/config/schemas`
739
+ ### HelperRest
3331
740
 
3332
- **[UPGRADE]**
741
+ `HelperRest` provides a unified REST client for plugins with built-in support for authentication, retries, failover, and proxies.
3333
742
 
3334
- - Configurationfiles for custom plugins should be changed
3335
- old syntax:
743
+ ```ts
744
+ helper.doRequest(baseEntity, method, path, body?, ctx?, options?)
745
+ ```
3336
746
 
3337
- "scimversion": "1.1",
3338
- new syntax:
747
+ - `baseEntity` — `'undefined'` if not used; must match a key in `endpoint.entity`
748
+ - `method` — `GET`, `POST`, `PATCH`, `PUT`, `DELETE`
749
+ - `path` — full URL or path appended to `baseUrl`
750
+ - `body` — optional request body
751
+ - `ctx` — optional, passes the `Authorization` header for PassThrough auth
752
+ - `options` — optional overrides for connection settings
753
+
754
+ **Endpoint connection structure:**
755
+
756
+ ```json
757
+ "endpoint": {
758
+ "entity": {
759
+ "undefined": {
760
+ "connection": {
761
+ "baseUrls": ["https://api.example.com"],
762
+ "auth": {
763
+ "type": "basic|oauth|token|bearer|oauthSamlBearer|oauthJwtBearer",
764
+ "options": { ... }
765
+ },
766
+ "options": {
767
+ "headers": {},
768
+ "tls": {} // // files located in ./config/certs
769
+ },
770
+ "proxy": {}
771
+ }
772
+ }
773
+ }
774
+ }
775
+ ```
3339
776
 
3340
- "scim": {
3341
- "version": "1.1",
3342
- "customSchema": null
3343
- },
3344
- Note, "1.1" is default, if using "2.0" the new syntax must be used.
777
+ #### Basic Auth
3345
778
 
779
+ ```json
780
+ "connection": {
781
+ "baseUrls": ["https://localhost:8880"],
782
+ "auth": {
783
+ "type": "basic",
784
+ "options": {
785
+ "username": "gwadmin",
786
+ "password": "password"
787
+ }
788
+ },
789
+ "options": {
790
+ "tls": { "rejectUnauthorized": false, "ca": "ca.pem" }
791
+ }
792
+ }
793
+ ```
3346
794
 
3347
- ### v2.0.2
3348
- [Fixed]
795
+ #### Entra ID — Client Secret
796
+
797
+ ```json
798
+ "connection": {
799
+ "baseUrls": [],
800
+ "auth": {
801
+ "type": "oauth",
802
+ "options": {
803
+ "azureTenantId": "<tenant-id>",
804
+ "clientId": "<client-id>",
805
+ "clientSecret": "<client-secret>"
806
+ }
807
+ }
808
+ }
809
+ ```
3349
810
 
3350
- - SCIM 2.0 incorrect response for user not found
3351
- - Did not mask logentries ending with newline
811
+ #### Entra ID Certificate Secret
812
+
813
+ ```json
814
+ "connection": {
815
+ "baseUrls": [],
816
+ "auth": {
817
+ "type": "oauthJwtBearer",
818
+ "options": {
819
+ "azureTenantId": "<tenant-id>",
820
+ "clientId": "<client-id>",
821
+ "tls": {
822
+ "key": "key.pem",
823
+ "cert": "cert.pem"
824
+ }
825
+ }
826
+ }
827
+ }
828
+ ```
3352
829
 
830
+ #### Entra ID — Federated Credentials (no secrets)
831
+
832
+ ```json
833
+ "connection": {
834
+ "baseUrls": [],
835
+ "auth": {
836
+ "type": "oauthJwtBearer",
837
+ "options": {
838
+ "azureTenantId": "<tenant-id>",
839
+ "fedCred": {
840
+ "issuer": "<https://FQDN-scimgateway>", // https://scimgateway.my-company.com
841
+ "subject": "<entra-application-object-id>",
842
+ "name": "<entra-fed-cred-unique-name>" // plugin-entra-id
843
+ }
844
+ }
845
+ }
846
+ }
847
+ ```
3353
848
 
3354
- ### v2.0.0
3355
- **[MAJOR]**
849
+ > The `issuer`, `subject`, and `name` must match the Federated Credentials configured in Entra ID (scenario: "Other issuer"). The gateway must be reachable from the internet at the `issuer` URL, or use Azure Relay for outbound-only communication.
3356
850
 
3357
- - Codebase moved from callback to async/await
3358
- - Koa replacing Express
3359
- - Some log enhancements
3360
- - Deprecated cipher methods have been replaced
3361
- - Plugin restful (REST) and
3362
- - forwardinc (SOAP) includes failover logic based on endpoints defined in array baseUrls/baseServiceEndpoints.
851
+ #### General OAuth (Client Credentials)
3363
852
 
3364
- **[UPGRADE]**
853
+ ```json
854
+ "connection": {
855
+ "baseUrls": ["https://api.example.com"],
856
+ "auth": {
857
+ "type": "oauth",
858
+ "options": {
859
+ "tokenUrl": "https://idp.example.com/oauth/token",
860
+ "clientId": "<client-id>",
861
+ "clientSecret": "<client-secret>"
862
+ }
863
+ }
864
+ }
865
+ ```
3365
866
 
3366
- Note, this is a major upgrade (^1.x.x => ^2.x.x) and will brake compatibility with any existing custom plugins. To force a major upgrade, suffix `@latest` must be include in the npm install command, but it's recommended to do a fresh install and copy any custom plugins instead of upgrading an existing package
867
+ Use VS Code IntelliSense on `HelperRest.doRequest()` for full type and option documentation.
3367
868
 
3368
- cd c:\my-scimgateway
3369
- npm install scimgateway@latest
869
+ ---
3370
870
 
3371
- Custom plugins needs some changes (please see included example plugins)
871
+ ### Single Binary Deployment
3372
872
 
3373
- - `scimgateway.on(xxx, function (..., callback)` replaced with `scimgateway.xxx = async (...)` returning a result or throwing an error
3374
- - Rest and SOAP using `doRequest` method having endpoint failover logic through array `baseUrls/baseServiceEndpoints` defined in corresponding plugin configuration file.
3375
- - Additional argument `attributes` included in exploreUsers and exploreGroups method
3376
- - Proxy configuration includes option for user/password
3377
- - Encrypted passwords in configuration files needs to be reset to clear text passwords
873
+ Compile a plugin to a self-contained native binary (no Bun/Node runtime required):
3378
874
 
875
+ ```sh
876
+ cd my-scimgateway
3379
877
 
3380
- ### v1.0.20
3381
- [Fixed]
878
+ bun build --compile ./lib/plugin-loki.ts \
879
+ --target=bun-darwin-arm64 \
880
+ --outfile ./build/plugin-loki
3382
881
 
3383
- - HTTP status code 200 and totalResults set to value of 0 when using SCIM 2.0 filter user/group and no resulted user/group found. SCIM 1.1 still using status code 404.
882
+ # See https://bun.sh/docs/bundler/executables#cross-compile-to-other-platforms for all targets
3384
883
 
3385
- **[UPGRADE]**
884
+ cp -r ./config ./build
3386
885
 
3387
- - For custom plugins to be compliant with SCIM 2.0, the `getUser` and `getGroup` methods needs to be updated. If user/group not found then return `callback(null, null)` instead of callback(err)
886
+ cd build
887
+ ./plugin-loki # binary name must match the config file prefix
888
+ ```
3388
889
 
890
+ The `config/` directory must be in the same folder as the binary.
3389
891
 
3390
- ### v1.0.19
3391
- [Fixed]
892
+ ---
3392
893
 
3393
- - Fix related to external configuration (ref. v1.0.18) when running multiple plugins
894
+ ## Running the Gateway
3394
895
 
3395
- ### v1.0.18
3396
- [Improved]
896
+ ### Manual Startup
3397
897
 
3398
- - Includes latest versions of module dependencies
3399
- - Loglevel configuration for file and console now separated
3400
- - Loglevel colorize option (value false could be useful when redirecting console output)
3401
- - All configuration can be set based on environment variables
3402
- - All configuration can be set based on correspondig json-content in external file (supports also dot notation)
898
+ ```sh
899
+ # All three are equivalent:
900
+ bun c:\my-scimgateway
901
+ bun c:\my-scimgateway\index.ts
902
+ bun . # from the package root
903
+ ```
3403
904
 
3404
- **[UPGRADE]**
905
+ Press `Ctrl+C` to stop.
3405
906
 
3406
- - Configurationfiles for custom plugins should be changed
3407
- old syntax:
907
+ ### Windows Task Scheduler
3408
908
 
3409
- loglevel: "debug"
3410
- new syntax:
909
+ Open Task Scheduler (`taskschd.msc`), right-click "Task Scheduler Library" → "Create Task":
3411
910
 
3412
- "loglevel": {
3413
- "file": "debug",
3414
- "console": "error",
3415
- "colorize": true
3416
- }
911
+ | Tab | Setting |
912
+ |---|---|
913
+ | General | Name: `SCIM Gateway`; User: `SYSTEM`; Run with highest privileges |
914
+ | Triggers | Begin the task: At startup |
915
+ | Actions | Start a program: `<bun-install-path>\bun.exe`; Arguments: `c:\my-scimgateway` |
916
+ | Settings | Stop the task if it runs longer than: **Disabled** |
3417
917
 
3418
- ### v1.0.14
3419
- [Fixed]
918
+ **Verify:**
919
+ 1. Right-click → Run → confirm process appears in Task Manager
920
+ 2. Right-click → End → confirm process disappears
921
+ 3. Reboot → confirm auto-start
3420
922
 
3421
- - Some multiValued attributes not correctly handled (e.g. addresses)
3422
-
3423
- ### v1.0.13
3424
- [Fixed]
923
+ ---
3425
924
 
3426
- - plugin-azure-ad: New version of "Azure - ScimGateway.xml" fixing CA IM RoleDefGenerator problem (related to creating and importing screens in CA IM)
925
+ ## Docker
3427
926
 
3428
- **[UPGRADE]**
927
+ ### Single Image
3429
928
 
3430
- - Use CA ConnectorXpress, import "Azure - ScimGateway.xml" and deploy/redeploy endpoint
929
+ ```sh
930
+ mkdir /opt/my-scimgateway
931
+ cd /opt/my-scimgateway
932
+ bun init -y
933
+ bun install scimgateway
934
+ bun pm trust scimgateway
935
+ cp ./config/docker/* .
936
+ cp ./config/docker/.dockerignore .
3431
937
 
3432
- ### v1.0.12
3433
- [Fixed]
938
+ # Build
939
+ docker build --platform linux/amd64 --force-rm=true -t my-scimgateway:1.0.0 .
3434
940
 
3435
- - Incorrect logging of Express stream messages (type info) when running multiple plugins
941
+ # Create and run
942
+ docker create --init --ulimit memlock=-1:-1 --name my-scimgateway -p 8880:8880 my-scimgateway:1.0.0
943
+ docker start my-scimgateway
944
+ docker stop my-scimgateway
945
+ ```
3436
946
 
3437
- ### v1.0.11
3438
- [Fixed]
947
+ Consider passing `-e SEED=<random>` at create time if using encrypted configuration files.
3439
948
 
3440
- - plugin-azure-ad: proxy configuration did not work
949
+ ### Docker Compose
3441
950
 
951
+ Pre-requisites: `docker-compose` and `docker-ce`
3442
952
 
3443
- ### v1.0.10
3444
- [Fixed]
953
+ ```sh
954
+ mkdir /opt/my-scimgateway && cd /opt/my-scimgateway
955
+ bun init -y && bun install scimgateway && bun pm trust scimgateway
956
+ cp ./config/docker/* .
3445
957
 
3446
- - An issue with pagination fixed
958
+ adduser scimgateway
959
+ mkdir /home/scimgateway/config
3447
960
 
3448
- ### v1.0.9
3449
- [Improved]
961
+ # Copy your plugin config to the persistent volume
962
+ scp config/plugin-loki.json scimgateway@host:/home/scimgateway/config/
3450
963
 
3451
- - Cosmetics, changed emailOnError logic - now emitted by logger
964
+ docker-compose up --build -d
965
+ ```
3452
966
 
3453
- ### v1.0.8
3454
- [Improved]
967
+ Provided compose files:
968
+
969
+ | File | Purpose |
970
+ |---|---|
971
+ | `docker-compose.yml` | Main compose file — set exposed ports and environment here |
972
+ | `Dockerfile` | Main image definition |
973
+ | `DataDockerfile` | Volume mapping |
974
+ | `docker-compose-debug.yml` | Attach VS Code debugger |
975
+ | `docker-compose-mssql.yml` | Compose example including an MSSQL container |
976
+
977
+ **Common Docker commands:**
978
+
979
+ ```sh
980
+ docker ps # list running containers
981
+ docker images # list images
982
+ docker logs scimgateway # view logs
983
+ docker exec scimgateway <command> # run command in container
984
+ docker-compose stop / start # stop / restart
985
+ docker-compose -f docker-compose.yml \
986
+ -f docker-compose-debug.yml up -d # debug mode (VS Code)
987
+
988
+ # Upgrade — remove old container and dangling images first
989
+ docker rm scimgateway
990
+ docker rm $(docker ps -a -q)
991
+ docker rmi $(docker images -q -f "dangling=true")
992
+ ```
3455
993
 
3456
- - Support health monitoring using the "/ping" URL with a "hello" response, e.g. http://localhost:8880/ping. Useful for frontend load balancing/failover functionality
3457
- - Option for error notifications by email
994
+ ---
3458
995
 
3459
- **[UPGRADE]**
996
+ ## Identity Provider Integration
3460
997
 
3461
- - Configuration files for custom plugins must include the **emailOnError** object for enabling error notifications by email. Please see the syntax in provided example plugins and details described in the "Configuration" section of this document.
3462
-
3463
-
3464
- ### v1.0.7
3465
- [Improved]
998
+ ### Microsoft Entra ID as IdP
3466
999
 
3467
- - Docker now using node v.9.10.0 instead of v.6.9.2
3468
- - Minor log cosmetics
1000
+ Entra ID can automatically provision users to SCIM Gateway, which then forwards to your endpoint plugin.
3469
1001
 
3470
- ### v1.0.6
3471
- [Fixed]
1002
+ **Plugin configuration requirements:**
3472
1003
 
3473
- - Azure AD plugin, failed to create user when licenses (app Service plans) was included
1004
+ ```json
1005
+ "scimgateway": {
1006
+ "scim": { "version": "2.0" },
1007
+ "auth": {
1008
+ "bearerToken": [
1009
+ { "token": "shared-secret" }
1010
+ ],
1011
+ "bearerJwt": [
1012
+ { "azureTenantId": "xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx" }
1013
+ ]
1014
+ }
1015
+ }
1016
+ ```
3474
1017
 
3475
- ### v1.0.5
3476
- [Improved]
1018
+ - `token` must match the "Secret Token" in the Entra ID provisioning configuration
1019
+ - `azureTenantId` must match the Entra tenant ID
1020
+ - If "Secret Token" is left blank in Entra ID, JWT (`azureTenantId`) is used automatically
3477
1021
 
3478
- - Supporting GET /Users, GET /Groups, PUT method and delete groups
3479
- - After more than 3 invalid auth attempts, response will be delayed to prevent brute force
1022
+ **Azure Portal paths:**
3480
1023
 
3481
- [Fixed]
1024
+ ```
1025
+ Secret Token: Microsoft Entra ID → Enterprise Apps → <App> → Provisioning → Secret Token
1026
+ Tenant ID: Microsoft Entra ID → Overview → Tenant ID
1027
+ Attribute maps: Enterprise Apps → <App> → Provisioning → Edit attribute mappings → Mappings
1028
+ ```
3482
1029
 
3483
- - Some minor compliance fixes
1030
+ **Required attribute mappings:**
3484
1031
 
3485
- **Thanks to [@ywchuang](https://github.com/ywchuang)**
1032
+ | Object | Source | Target | Matching |
1033
+ |---|---|---|---|
1034
+ | User | `userPrincipalName` | `userName` | Precedence #1 |
1035
+ | Group | `displayName` | `displayName` | Precedence #1 |
1036
+ | Group | `members` | `members` | — |
3486
1037
 
3487
- ### v1.0.4
3488
- [Improved]
1038
+ **Entra ID behavior notes:**
1039
+ - Deleting a user sends `PATCH { "active": "False" }` rather than a `DELETE` request
1040
+ - Entra ID periodically checks for non-existent users/groups as a keep-alive
1041
+ - Entra ID checks existence before creating (no full user explore like some other IdPs)
3489
1042
 
3490
- - Plugin for Azure AD now supports paging for retrieving users and groups. Any existing metafile used by CA ConnectorXpress ("Azure - ScimGateway.xml") must be re-deployed.
1043
+ ---
3491
1044
 
3492
- [Fixed]
1045
+ ### Symantec/Broadcom Identity Manager as IdP
3493
1046
 
3494
- - Don't use deprecated existsSync in postinstallation
1047
+ Use **SCIM version `"1.1"`** for Symantec/Broadcom Provisioning.
3495
1048
 
3496
- ### v1.0.3
3497
- [Fixed]
1049
+ In Provisioning Manager use endpoint type `SCIM (DYN Endpoint)` or create a custom type.
3498
1050
 
3499
- - Undefined root url not handled correctly after v1.0.0
1051
+ **Example endpoint configuration (plugin-loki):**
3500
1052
 
3501
- ### v1.0.2
3502
- [Fixed]
1053
+ ```
1054
+ Endpoint Name: Loki-8880
1055
+ User Name: gwadmin
1056
+ Password: password
1057
+ SCIM Authentication Method: HTTP Basic Authentication
1058
+ SCIM Based URL: http://localhost:8880
1059
+ or: http://localhost:8880/<baseEntity>
1060
+ ```
3503
1061
 
3504
- - License and group defined as capability attributes in metafile used by CA ConnectorXpress regarding plugin-azure-ad
1062
+ The `baseEntity` parameter enables multi-tenant setups create multiple endpoints with the same base URL but different `baseEntity` values (e.g. `/client-a`, `/client-b`). Define per-entity connection attributes in the plugin JSON configuration.
3505
1063
 
3506
- ### v1.0.1
3507
- [Fixed]
1064
+ ---
3508
1065
 
3509
- - Mocha test script did not terminate after upgrading from 3.x to 4.x of Mocha
1066
+ ## Entra ID Provisioning Plugin
3510
1067
 
3511
- ### v1.0.0
3512
- [Improved]
1068
+ `plugin-entra-id` provisions users and groups to Microsoft Entra ID via the Microsoft Graph API.
3513
1069
 
3514
- - New plugin-azure-ad.js for Azure AD user provisioning including Azure license management e.g. Office 365
3515
- - Includes latest versions of module dependencies
3516
- - Module hdb (for SapHana) and saml is not included by default anymore and therefore have to be manually installed if needed.
1070
+ ### Entra ID App Registration
3517
1071
 
3518
- **[UPGRADE]**
3519
- Method `getGroupMembers` must be updated for all custom plugins
1072
+ 1. **Microsoft Entra ID → App registrations → New registration**
1073
+ - Name: `SCIM Gateway Inbound`
1074
+ - Accounts: This organizational directory only
1075
+ 2. **Overview** — copy Application (client) ID and Directory (tenant) ID
1076
+ 3. **Certificates & secrets → New client secret** — copy the value
1077
+ 4. **API permissions → Add → Microsoft Graph → Application permissions:**
1078
+ - `Directory.ReadWriteAll`
1079
+ - `Organization.ReadWrite.All`
1080
+ - Additional for signInActivity, roles, licenses and access packages:
1081
+ - `AuditLog.Read.All` *(only if using `map.user.signInActivity`; requires Entra ID Premium)*
1082
+ - `RoleEligibilitySchedule.ReadWrite.Directory` *(PIM Eligible roles; only if using `map.user.roles`)*
1083
+ - `RoleManagement.ReadWrite.Directory` *(PIM Permanent roles; only if using `map.user.roles`)*
1084
+ - `EntitlementManagement.ReadWrite.All` *(IGA Access Packages; only if using `map.user.entitlements`)*
1085
+ - Click **Grant admin consent**
1086
+ 5. **Entra ID → Roles and administrators → User administrator → Add assignments** — add `SCIM Gateway Inbound`
3520
1087
 
3521
- Replace:
1088
+ > For full access to admin users, assign the `Global Administrator` role. The `User Administrator` role has limitations on users with admin roles.
3522
1089
 
3523
- scimgateway.on('getGroupMembers', function (baseEntity, id, attributes, startIndex, count, callback) {
3524
- ...
3525
- let ret = {
3526
- 'Resources' : [],
3527
- 'totalResults' : null
3528
- }
3529
- ...
3530
- ret.Resources.push(userGroup)
3531
- ...
3532
- callback(null, ret)
1090
+ > `signInActivity, roles, licenses and access packages` requires permissions above. Note, `ReadWrite` can be replaced with `Read` if management is not required. **Remove any mapping configuration whose conditions are not met** — Minimum read permissions are validated at startup.
3533
1091
 
1092
+ ### Plugin Configuration
3534
1093
 
3535
- With:
1094
+ **`index.ts`:**
3536
1095
 
3537
- scimgateway.on('getGroupMembers', function (baseEntity ,id ,attributes, callback) {
3538
- ...
3539
- let arrRet = []
3540
- ...
3541
- arrRet.push(userGroup)
3542
- ...
3543
- callback(null, arrRet)
1096
+ ```ts
1097
+ import './lib/plugin-entra-id.ts'
1098
+ export {}
1099
+ ```
3544
1100
 
3545
- ### v0.5.3
3546
- [Improved]
1101
+ **`config/plugin-entra-id.json` (key sections):**
3547
1102
 
3548
- - Includes api gateway/plugin for general none provisioning
3549
- - GET /api
3550
- - GET /api?queries
3551
- - GET /api/{id}
3552
- - POST /api + body
3553
- - PUT /api/{id} + body
3554
- - PATCH /api/{id} + body
3555
- - DELETE /api/{id}
3556
- - plugin-api.js demonstrates api functionallity (becomes what you want it to become)
1103
+ ```json
1104
+ {
1105
+ "scimgateway": {
1106
+ "scim": { "version": "2.0", "skipTypeConvert": true}, // skipTypeConvert if Access Package management (entitlements)
1107
+ "auth": {
1108
+ "basic": [
1109
+ {
1110
+ "username": "gwadmin",
1111
+ "password": "password",
1112
+ "readOnly": false
1113
+ }
1114
+ ]
1115
+ }
1116
+ },
1117
+ "endpoint": {
1118
+ "entity": {
1119
+ "undefined": {
1120
+ "connection": {
1121
+ "baseUrls": [],
1122
+ "auth": {
1123
+ "type": "oauth",
1124
+ "options": {
1125
+ "azureTenantId": "<Tenant ID>",
1126
+ "clientId": "<Application ID>",
1127
+ "clientSecret": "<Secret value>"
1128
+ }
1129
+ },
1130
+ "proxy": {
1131
+ "host": null,
1132
+ "username": null,
1133
+ "password": null
1134
+ }
1135
+ }
1136
+ }
1137
+ }
1138
+ }
1139
+ }
1140
+ ```
3557
1141
 
1142
+ `clientSecret` and any proxy passwords are automatically encrypted on the first connection.
3558
1143
 
3559
- ### v0.5.2
3560
- [Improved]
1144
+ **Multi-tenant setup:**
3561
1145
 
3562
- - One or more of following authentication/authorization methods are accepted:
3563
- - Basic Authentication
3564
- - Bearer token - shared secret
3565
- - Bearer token - Standard JSON Web Token (JWT)
3566
- - Bearer token - Azure JSON Web Token (JWT)
1146
+ ```json
1147
+ "endpoint": {
1148
+ "entity": {
1149
+ "undefined": { ... },
1150
+ "client-a": { ... },
1151
+ "client-b": { ... }
1152
+ }
1153
+ }
1154
+ ```
3567
1155
 
3568
- **[UPGRADE]**
1156
+ ### Using with Symantec/Broadcom (ConnectorXpress)
3569
1157
 
3570
- - Configuration files for custom plugins `config/plugin-xxx.json` needs to be updated regarding the new `scimgateway.auth` section:
3571
- - Copy scimgateway.auth section from one of the example plugins
3572
- - Copy existing scimgateway.username value to new auth.basic.username value
3573
- - Copy existing scimgateway.password value to new auth.basic.username value
3574
- - Copy existing scimgateway.oauth.accesstoken value to new auth.bearer.token value
3575
- - Delete scimgateway.username
3576
- - Delete scimgateway.password
3577
- - Delete scimgateway.oauth
1158
+ 1. Start SCIM Gateway with `plugin-entra-id`
1159
+ 2. Open ConnectorXpress Setup Data Sources Add Layer7 → Base URL: `http://localhost:8881`
1160
+ 3. Import the endpoint type metadata: `node_modules/scimgateway/config/resources/Azure - ScimGateway.xml`
1161
+ 4. Create endpoint type `Azure - ScimGateway`
3578
1162
 
1163
+ **Provisioning Manager endpoint example:**
3579
1164
 
3580
- ### v0.4.6
3581
- [Improved]
1165
+ ```
1166
+ Endpoint Name: AzureAD-8881
1167
+ User Name: gwadmin
1168
+ Password: password
1169
+ SCIM Authentication Method: HTTP Basic Authentication
1170
+ SCIM Based URL: http://localhost:8881
1171
+ ```
3582
1172
 
3583
- - Document updated on how to run SCIM Gateway as a Docker container
3584
- - `config\docker` includes docker configuration examples
3585
- **Thanks to [@cwatsonc](https://github.com/cwatsonc) and [@visualjeff](https://github.com/visualjeff)**
1173
+ ---
3586
1174
 
1175
+ ## API Gateway
3587
1176
 
3588
- ### v0.4.5
3589
- [Improved]
1177
+ SCIM Gateway doubles as a general API gateway via the `/api` path (no SCIM schema required):
3590
1178
 
3591
- - Environment variable `SEED` overrides default password seeding
3592
- - Setting SCIM Gateway port to `"process.env.XXX"` lets environment variable XXX define the port
3593
- - Don't validate config-file port number for numeric value (Azure AD - iisnode using a name pipe for communication)
1179
+ ```
1180
+ GET /api
1181
+ GET /api?<query>
1182
+ GET /api/{id}
1183
+ POST /api + body
1184
+ PUT /api/{id} + body
1185
+ PATCH /api/{id} + body
1186
+ DELETE /api/{id}
1187
+ ```
3594
1188
 
3595
- **[UPGRADE]**
1189
+ With `baseEntity`: `/<baseEntity>/api`
3596
1190
 
3597
- - Configuration files for custom plugins `config/plugin-xxx.json` needs to be updated:
3598
- - Encrypted passwords needs to be reset to clear text passwords
3599
- - Start SCIM Gateway and passwords will become encrypted
1191
+ A public (unauthenticated) API path is also available:
3600
1192
 
3601
- ### v0.4.4
3602
- [Improved]
1193
+ ```
1194
+ GET /pub/api?model=Tesla
1195
+ ```
3603
1196
 
3604
- - NoSQL Document-Oriented Database plugin: `plugin-loki`
3605
- This plugin now replace previous `plugin-testmode`
3606
- **Thanks to [@visualjeff](https://github.com/visualjeff)**
3607
- - Minor code/comment reorganizations in provided plugins
3608
- - Minor adjustments to multi-value logic introduced in v0.4.0
1197
+ See `lib/plugin-api.ts` for a complete example.
3609
1198
 
3610
- **[UPGRADE]**
1199
+ ---
3611
1200
 
3612
- - Delete depricated `lib/plugin-testmode.js` and `config/plugin-testmode.json`
3613
- - Edit index.js, replace tesmode with loki
1201
+ ## Building Custom Plugins
3614
1202
 
3615
- ### v0.4.2
3616
- [Fixed]
1203
+ **Recommended editor:** [Visual Studio Code](https://code.visualstudio.com/) — provides IntelliSense for all `scimgateway` methods.
3617
1204
 
3618
- - plugin-restful minor adjustments to multivalue and cleared attributes logic introduced in v0.4.0
1205
+ ### Setup
3619
1206
 
3620
- ### v0.4.1
3621
- [Improved]
1207
+ 1. Copy the closest matching example plugin (e.g. `lib/plugin-mssql.ts` + `config/plugin-mssql.json`) and rename both with your prefix (e.g. `plugin-mine`)
1208
+ 2. Set a unique `port` in `config/plugin-mine.json`
1209
+ 3. Add your plugin to `index.ts`: `import './lib/plugin-mine.ts'`
1210
+ 4. Start the gateway and verify
3622
1211
 
3623
- - Mocha test scripts for automated testing of plugin-testmode
3624
- - Automated tests run on Travis-ci.org (click on build badge)
3625
- - **Thanks to [@visualjeff](https://github.com/visualjeff)**
1212
+ ### Mandatory Plugin Initialization
3626
1213
 
1214
+ ```ts
1215
+ // start - mandatory plugin initialization
1216
+ import { ScimGateway, HelperRest } from 'scimgateway'
1217
+ const scimgateway = new ScimGateway()
1218
+ const helper = new HelperRest(scimgateway) // include if using REST
1219
+ const config = scimgateway.getConfig()
1220
+ scimgateway.authPassThroughAllowed = false
1221
+ scimgateway.pluginAndOrFilterEnabled = false
1222
+ // end - mandatory plugin initialization
1223
+ ```
3627
1224
 
3628
-
3629
- [Fixed]
1225
+ ### Implementation Order
3630
1226
 
3631
- - Minor adjustments to multi-value logic introduced in v0.4.0
1227
+ Build and test incrementally:
3632
1228
 
3633
- ### v0.4.0
3634
- [Improved]
1229
+ 1. **`getGroups`** — return empty response to disable group handling initially (see `plugin-saphana` for a groups-free example)
1230
+ 2. **`getUsers`** — retrieve all accounts and a single account by filter
1231
+ 3. **`createUser`** — create new accounts
1232
+ 4. **`deleteUser`** — delete accounts
1233
+ 5. **`modifyUser`** — update accounts
1234
+ 6. **`getGroups`** — re-enable with real logic if groups are supported
1235
+ 7. **`createGroup`**, **`deleteGroup`**, **`modifyGroup`** — group lifecycle
3635
1236
 
3636
- - Not using the SCIM standard for handling multivalue attributes and cleared attributes. Changed from array to object based on type. This simplifies plugin-coding for multivalue attributes like emails, phoneNumbers, entitlements, ...
3637
- - Module dependencies updated to latest versions
1237
+ ### Plugin Methods
3638
1238
 
3639
- **[UPGRADE]**
1239
+ **SCIM methods (implement in your plugin):**
3640
1240
 
3641
- - Custom plugins using multivalue attributes needs to be updated regarding methods createUser and modifyUser. Please see example plugins for details.
1241
+ | Method | Description |
1242
+ |---|---|
1243
+ | `scimgateway.getUsers()` | Retrieve users (all or filtered) |
1244
+ | `scimgateway.createUser()` | Create a new user |
1245
+ | `scimgateway.deleteUser()` | Delete a user |
1246
+ | `scimgateway.modifyUser()` | Update user attributes |
1247
+ | `scimgateway.getGroups()` | Retrieve groups (all or filtered) |
1248
+ | `scimgateway.createGroup()` | Create a new group |
1249
+ | `scimgateway.deleteGroup()` | Delete a group |
1250
+ | `scimgateway.modifyGroup()` | Update group members/attributes |
1251
+ | `scimgateway.getEntitlements()` | Retrieve entitlements (e.g. Entra ID licenses) |
1252
+ | `scimgateway.getRoles()` | Retrieve roles (e.g. Entra ID PIM roles) |
3642
1253
 
3643
- ### v0.3.8
3644
- [Fixed]
1254
+ **API Gateway methods:**
3645
1255
 
3646
- - Minor changes related to SCIM specification
1256
+ | Method | Path |
1257
+ |---|---|
1258
+ | `scimgateway.getApi()` | `GET /api` |
1259
+ | `scimgateway.postApi()` | `POST /api` |
1260
+ | `scimgateway.putApi()` | `PUT /api/{id}` |
1261
+ | `scimgateway.patchApi()` | `PATCH /api/{id}` |
1262
+ | `scimgateway.deleteApi()` | `DELETE /api/{id}` |
1263
+ | `scimgateway.publicApi()` | `GET /pub/api` (no auth) |
3647
1264
 
3648
- ### v0.3.7
3649
- [Improved]
1265
+ Use VS Code IntelliSense on any method for inline documentation and type information.
3650
1266
 
3651
- - PFX / PKCS#12 certificate bundle is supported
1267
+ ### Custom Schemas
3652
1268
 
3653
- ### v0.3.6
3654
- [Improved]
1269
+ If plugin use `endpointMapper`, SCIM schemas will be generated based on configured mapping.
3655
1270
 
3656
- - SCIM Gateway used by Microsoft Azure Active Directory is supported
3657
- - SCIM version 2.0 is supported
3658
- - Create group is supported
1271
+ To use custom SCIM schemas, copy `node_modules/scimgateway/lib/scimdef-v2.json` (or `scimdef-v1.json`) to `lib/` and edit as needed. The gateway will use your version when it detects the file.
3659
1272
 
3660
- **[UPGRADE]**
1273
+ ---
3661
1274
 
3662
- - For custom plugins to support create group, they needs to be updated regarding listener method `scimgateway.on('createGroup',...` Please see example plugins for details.
1275
+ ## License
3663
1276
 
1277
+ MIT © [Jarle Elshaug](https://www.elshaug.xyz)
3664
1278
 
1279
+ ---
3665
1280
 
3666
- ### v0.3.5
3667
- [Fixed]
1281
+ ## Change Log
3668
1282
 
3669
- - plugin-mssql not included in postinstall
1283
+ ### v6.2.2
1284
+ - **[Improved]** `plugin-entra-id` now supports Entra ID IGA Access Packages. For required API permissions, see [Entra ID App Registration](#entra-id-app-registration)
3670
1285
 
3671
- ### v0.3.4
3672
- [Improved]
1286
+ ### v6.2.1
1287
+ - `HelperRest`: fixed minor log cosmetics introduced in v6.2.0
3673
1288
 
3674
- - MSSQL example plugin: `plugin-mssql`
3675
- - Changed multivalue logic in example plugins, now using `scimgateway.getArrayObject`
1289
+ ### v6.2.0
1290
+ - **[Fixed]** `HelperRest`: failed on Bun v1.3.14 due to stricter Fetch standards compliance
1291
+ - **[Improved]** New `plugin-generic` replaces `plugin-scim`. Uses `endpointMapper` with the new `valueMap` option for group allowlisting and name mapping. Default config uses one-to-one SCIM mapping with plugin-loki as the target endpoint.
1292
+ - **[Improved]** `endpointMapper` now supports `valueMap`:
1293
+
1294
+ ```json
1295
+ "map": {
1296
+ "group": {
1297
+ "displayName": {
1298
+ "mapTo": "displayName",
1299
+ "type": "string",
1300
+ "valueMap": {
1301
+ "outboundEndpointGrp1": "inboundScimGrp1",
1302
+ "Employees": "Admins"
1303
+ }
1304
+ }
1305
+ }
1306
+ }
1307
+ ```
3676
1308
 
3677
- [Fixed]
1309
+ Clients only see and manage the SCIM-named groups (`inboundScimGrp1`, `Admins`), mapped to their endpoint counterparts (`outboundEndpointGrp1`, `Employees`). Useful for allowlisting specific groups or supporting different inbound/outbound names.
3678
1310
 
3679
- - Minor changes related to SCIM specification
1311
+ ### v6.1.20
1312
+ - `plugin-entra-id`: roles introduced in v6.1.19 were missing when retrieving a single user
3680
1313
 
1314
+ ### v6.1.19
1315
+ - **[Fixed]** SCIM v2.0 ResourceType endpoint schemas using incorrect id
1316
+ - **[Improved]** `GET /Roles` and `GET /Entitlements` endpoint support, with user management via SCIM `roles` and `entitlements` attributes
1317
+ - **[Improved]** `plugin-entra-id`: `entitlements` for Entra ID licenses (read-only); `roles` for Permanent and Eligible PIM roles (full management)
1318
+ - PIM Eligible roles: requires `RoleEligibilitySchedule.ReadWrite.All`
1319
+ - PIM Permanent roles: requires `RoleManagement.ReadWrite.Directory`
1320
+ - Remove `map.user.roles` if above conditions are not met
1321
+ - `skipSignInActivity` option (v6.1.17) no longer used; `signInActivity` and PIM role permissions are validated at startup
3681
1322
 
3682
- ### v0.3.3
3683
- [Fixed]
1323
+ ### v6.1.18
1324
+ - `createUser` and `modifyUser` now return the full user object, ensuring returned data reflects what was modified even when the endpoint hasn't internally synced yet
3684
1325
 
3685
- - Logic for handling incorrect pagination request to avoid endless loop conditions (there is a pagination bug in CA Identity Manager v.14)
3686
- - Pagination now supported on getGroupMembers
1326
+ ### v6.1.17
1327
+ - `plugin-entra-id`: fixed broken `filter=userName eq "user_upn"` introduced in v6.1.11 when using updated config with `map.user.signInActivity`
1328
+ - `plugin-entra-id`: new option `endpoint.entity.[baseEntity].skipSignInActivity = true` to exclude `signInActivity` (requires Entra ID Premium + `AuditLog.Read.All`)
3687
1329
 
3688
- **[UPGRADE]**
1330
+ ### v6.1.16
1331
+ - `plugin-entra-id`: `GET /Entitlements` now uses `derivedIncludes` with full recursive expansion
3689
1332
 
3690
- - Custom plugins needs to be updated regarding listener method `scimgateway.on('getGroupMembers',...` New arguments have been added "startIndex" and "count". Also a new return variable "ret". Please see example plugins for details.
1333
+ ### v6.1.15
1334
+ - `plugin-entra-id`: fixed `filter=entitlements pr`
3691
1335
 
3692
- ### v0.3.2
3693
- [Fixed]
1336
+ ### v6.1.14
1337
+ - Support for filter `attribute not pr`
1338
+ - Dependencies bump
3694
1339
 
3695
- - Minor changes related to SCIM specification
1340
+ ### v6.1.13
1341
+ - `plugin-entra-id`: `signInActivity` attributes are now filterable
3696
1342
 
3697
- ### v0.3.1
3698
- [Improved]
1343
+ ### v6.1.12
1344
+ - Filter operator `pr` (presence) now forwarded to plugins (previously rejected)
1345
+ - `plugin-entra-id`: handles `pr` filter on entitlements
3699
1346
 
3700
- - REST Webservices example plugin: `plugin-restful`
1347
+ ### v6.1.11
1348
+ - **[Fixed]** Incorrect schema generation when using `endpointMapper` (regression from v6.1.6)
1349
+ - **[Improved]** New `GET /Entitlements` endpoint and `scimgateway.getEntitlements()` method
1350
+ - `plugin-entra-id`: user license information via `entitlements`; remove `map.user.signInActivity` if Entra ID Premium is unavailable
3701
1351
 
3702
- ### v0.3.0
3703
- [Improved]
1352
+ ### v6.1.10
1353
+ - `plugin-entra-id`: group membership now includes nested (transitive) groups (`direct` and `indirect`)
1354
+ - Fixed missing Docker files: `config/docker/.dockerignore` and `docker-compose-mssql.yml`
3704
1355
 
3705
- - Preferred installation method changed from "global" to "local"
3706
- - `<Base URL>/[baseEntity]` for multi tenant or multi endpoint flexibility
3707
- - plugin-forwardinc includes examples of baseEntity, custom soap header and signed saml assertion
3708
- - Support groups defined on user object "group member of user"
3709
- - New module dependendcies included: saml, async and callsite
1356
+ ### v6.1.9
1357
+ - `createUser`/`createGroup` responses now correctly include the generated ID
3710
1358
 
1359
+ ### v6.1.8 / v6.1.7
1360
+ - Fixed incorrect masking of secrets in request info log messages
1361
+ - `plugin-entra-id`: fixed edge case where `createUser` with a manager could fail
3711
1362
 
3712
- **[UPGRADE]**
1363
+ ### v6.1.6
1364
+ - Fixed `plugin-loki` and `plugin-mongodb` returning empty results when using extension schema attributes in search
1365
+ - Auth failure due to `readOnly` now returns HTTP 405 instead of 401
1366
+ - `postinstall` ensures `"type": "module"` is set in `package.json`
1367
+ - `endpointMapper` now generates a custom schema; supports `"x-agent-schema"` for AI MCP tool instructions
3713
1368
 
3714
- - Use "fresh" install and restore any custom plugins. Custom plugins needs to be updated. Listener method names have changed and method must include "baseEntity" - please see example plugins.
1369
+ ### v6.1.5
1370
+ - Complex filtering (`and`/`or`) handled by the gateway using the plugin's simple filter logic
1371
+ - `modifyGroup` now returns HTTP 204 instead of 200
1372
+ - New `/auth` endpoint for validating external authentication
1373
+ - `plugin-entra-id`: supports `sw` (startsWith) filter
3715
1374
 
3716
- ### v0.2.2 - v0.2.8
3717
- [Doc]
1375
+ ### v6.1.4
1376
+ - Fixed OData paging in `plugin-entra-id` and `helper-rest` — missing users/groups/members in large directories
1377
+ - Fixed incomplete group membership when paging not fully iterated
3718
1378
 
3719
- - Minor readme changes and version bumps
1379
+ ### v6.1.3
1380
+ - Azure Relay: improved recovery on failure
1381
+ - `plugin-ldap`: improvements for Active Directory and `objectGUID`/`mS-DS-ConsistencyGuid`
1382
+ - `modifyGroup`: adding an existing member or removing a non-existent member now returns 200 OK instead of an error
3720
1383
 
3721
- ### v0.2.1
3722
- [Fixed]
1384
+ ### v6.1.2
1385
+ - Fixed SMTP mail failure caused by an updated dependency
1386
+ - Fixed `endpointMapper` when `mapTo` contained multiple comma-separated attributes including a multivalued one
3723
1387
 
3724
- - plugin-forwardinc explore of empty endpoint
1388
+ ### v6.1.1
1389
+ - `plugin-ldap`: fixed race condition where `createUser` immediately followed by `readUser` could fail on some systems (e.g. Samba AD)
1390
+ - Final info log message now includes full JSON serialization (durationMs, status, requestBody, responseBody, …)
3725
1391
 
3726
- ### v0.2.0
3727
- Initial version
1392
+ ### v6.1.0
1393
+ - `tsx` included — SCIM Gateway now runs as ES module (TypeScript) in Node.js: `node --import=tsx ./index.ts`
1394
+ - Simplified mandatory plugin initialization using static `import`
1395
+ - `index.ts` updated to use static imports
1396
+ - Bun binary builds now supported (see [Single Binary Deployment](#single-binary-deployment))
1397
+
1398
+ ### v6.0.0 — Major
1399
+ - API method response bodies returned as-is (previously wrapped in `{ result: <content> }`) — **clients parsing responses must be updated**
1400
+ - New `scimgateway.publicApi()` for unauthenticated `/pub/api` routes
1401
+ - `bearerJwtAzure.tenantIdGUID` replaced by `bearerJwt.azureTenantId` — **existing configurations must be updated**
1402
+
1403
+ ### v5.x — Previous Major Series
1404
+ For v5.x change history (Bun/TypeScript migration, Azure Relay, Bulk Operations, SCIM Stream, HelperRest, Docker, email OAuth, and more), see the [GitHub commit history](https://github.com/jelhub/scimgateway/commits/master/).