From 1a2e96474e8bb60f3bb466aa45dbb884e6ace153 Mon Sep 17 00:00:00 2001 From: Falk Wolsky Date: Wed, 2 Jul 2025 11:02:54 +0200 Subject: [PATCH 01/11] Update docker-images.yml Adding Enterprise Edition Docker Image --- .github/workflows/docker-images.yml | 18 ++++++++++++++++++ 1 file changed, 18 insertions(+) diff --git a/.github/workflows/docker-images.yml b/.github/workflows/docker-images.yml index d075f1fdc..97b1c85aa 100644 --- a/.github/workflows/docker-images.yml +++ b/.github/workflows/docker-images.yml @@ -146,6 +146,24 @@ jobs: push: true tags: ${{ env.FRONTEND_IMAGE_NAMES }} + - name: Build and push the enterprise edition frontend image + if: ${{ env.BUILD_FRONTEND == 'true' }} + uses: docker/build-push-action@v6 + env: + NODE_ENV: production + with: + file: ./deploy/docker/Dockerfile + target: lowcoder-ce-frontend + build-args: | + REACT_APP_ENV=production + REACT_APP_EDITION=enterprise + REACT_APP_COMMIT_ID="dev #${{ env.SHORT_SHA }}" + platforms: | + linux/amd64 + linux/arm64 + push: true + tags: ${{ env.FRONTEND_IMAGE_NAMES }} + - name: Build and push the node service image if: ${{ env.BUILD_NODESERVICE == 'true' }} uses: docker/build-push-action@v6 From 3ca7794a3b4ad5896928ed557b42da9a6e402830 Mon Sep 17 00:00:00 2001 From: RAHEEL Date: Wed, 2 Jul 2025 15:14:40 +0500 Subject: [PATCH 02/11] added ee build command --- client/package.json | 1 + 1 file changed, 1 insertion(+) diff --git a/client/package.json b/client/package.json index 12f93a4aa..08ebeac4c 100644 --- a/client/package.json +++ b/client/package.json @@ -15,6 +15,7 @@ "start:ee": "REACT_APP_EDITION=enterprise yarn workspace lowcoder start", "translate": "node --loader ts-node/esm ./scripts/translate.js", "build": "yarn node ./scripts/build.js", + "build:ee": "REACT_APP_EDITION=enterprise yarn node ./scripts/build.js", "test": "jest && yarn workspace lowcoder-comps test", "prepare": "yarn workspace lowcoder prepare", "build:core": "yarn workspace lowcoder-core build", From c6d018c7a62de99deea19bdbda475ccee1a343d9 Mon Sep 17 00:00:00 2001 From: RAHEEL Date: Wed, 2 Jul 2025 15:27:52 +0500 Subject: [PATCH 03/11] updated Dockerfile to add separate lowcoder-ee-frontend image --- deploy/docker/Dockerfile | 78 ++++++++++++++++++++++++++++++++++++++++ 1 file changed, 78 insertions(+) diff --git a/deploy/docker/Dockerfile b/deploy/docker/Dockerfile index 5ecbbd579..2c26de959 100644 --- a/deploy/docker/Dockerfile +++ b/deploy/docker/Dockerfile @@ -185,6 +185,84 @@ EXPOSE 3443 ############################################################################# +## +## Build lowcoder client (Enterprise) application +## +FROM node:20.2-slim AS build-client-ee + +# curl is required for yarn build to succeed, because it calls it while building client +RUN apt-get update && apt-get install -y --no-install-recommends curl ca-certificates + +# Build client +COPY ./client /lowcoder-client-ee +WORKDIR /lowcoder-client-ee +RUN yarn --immutable + +ARG REACT_APP_COMMIT_ID=test +ARG REACT_APP_ENV=production +ARG REACT_APP_EDITION=community +ARG REACT_APP_DISABLE_JS_SANDBOX=true +RUN yarn build:ee + +# Build lowcoder-comps +WORKDIR /lowcoder-client-ee/packages/lowcoder-comps +RUN yarn install +RUN yarn build +RUN tar -zxf lowcoder-comps-*.tgz && mv package lowcoder-comps + +# Build lowcoder-sdk +WORKDIR /lowcoder-client-ee/packages/lowcoder-sdk +RUN yarn install +RUN yarn build + +WORKDIR /lowcoder-client-ee/packages/lowcoder-sdk-webpack-bundle +RUN yarn install +RUN yarn build + +## +## Intermediary Lowcoder client (Enterprise) image +## +## To create a separate image out of it, build it with: +## DOCKER_BUILDKIT=1 docker build -f deploy/docker/Dockerfile -t lowcoderorg/lowcoder-ee-frontend --target lowcoder-ee-frontend . +## +FROM nginx:1.27.1 AS lowcoder-ee-frontend +LABEL maintainer="lowcoder" + +# Change default nginx user into lowcoder user and remove default nginx config +RUN usermod --login lowcoder --uid 9001 nginx \ + && groupmod --new-name lowcoder --gid 9001 nginx \ + && rm -f /etc/nginx/nginx.conf \ + && mkdir -p /lowcoder/assets + +# Copy lowcoder client +COPY --chown=lowcoder:lowcoder --from=build-client-ee /lowcoder-client-ee/packages/lowcoder/build/ /lowcoder/client +# Copy lowcoder components +COPY --chown=lowcoder:lowcoder --from=build-client-ee /lowcoder-client-ee/packages/lowcoder-comps/lowcoder-comps /lowcoder/client-comps +# Copy lowcoder SDK +COPY --chown=lowcoder:lowcoder --from=build-client-ee /lowcoder-client-ee/packages/lowcoder-sdk /lowcoder/client-sdk +# Copy lowcoder SDK webpack bundle +COPY --chown=lowcoder:lowcoder --from=build-client-ee /lowcoder-client-ee/packages/lowcoder-sdk-webpack-bundle/dist /lowcoder/client-embed + + +# Copy additional nginx init scripts +COPY deploy/docker/frontend/00-change-nginx-user.sh /docker-entrypoint.d/00-change-nginx-user.sh +COPY deploy/docker/frontend/01-update-nginx-conf.sh /docker-entrypoint.d/01-update-nginx-conf.sh + +RUN chmod +x /docker-entrypoint.d/00-change-nginx-user.sh && \ + chmod +x /docker-entrypoint.d/01-update-nginx-conf.sh + +COPY deploy/docker/frontend/server.conf /etc/nginx/server.conf +COPY deploy/docker/frontend/nginx-http.conf /etc/nginx/nginx-http.conf +COPY deploy/docker/frontend/nginx-https.conf /etc/nginx/nginx-https.conf +COPY deploy/docker/frontend/ssl-certificate.conf /etc/nginx/ssl-certificate.conf +COPY deploy/docker/frontend/ssl-params.conf /etc/nginx/ssl-params.conf + + +EXPOSE 3000 +EXPOSE 3444 + +############################################################################# + ## ## Build Lowcoder all-in-one image ## From b8211088d01141bf5a141e7427ca3fa9c3fb75f4 Mon Sep 17 00:00:00 2001 From: RAHEEL Date: Wed, 2 Jul 2025 15:35:34 +0500 Subject: [PATCH 04/11] updated github workflow ee image --- .github/workflows/docker-images.yml | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/.github/workflows/docker-images.yml b/.github/workflows/docker-images.yml index 97b1c85aa..e26af7636 100644 --- a/.github/workflows/docker-images.yml +++ b/.github/workflows/docker-images.yml @@ -80,18 +80,21 @@ jobs: # Image names ALLINONE_IMAGE_NAMES=lowcoderorg/lowcoder-ce:${IMAGE_TAG} FRONTEND_IMAGE_NAMES=lowcoderorg/lowcoder-ce-frontend:${IMAGE_TAG} + FRONTEND_EE_IMAGE_NAMES=lowcoderorg/lowcoder-ee-frontend:${IMAGE_TAG} APISERVICE_IMAGE_NAMES=lowcoderorg/lowcoder-ce-api-service:${IMAGE_TAG} NODESERVICE_IMAGE_NAMES=lowcoderorg/lowcoder-ce-node-service:${IMAGE_TAG} if [[ "${IS_LATEST}" == "true" ]]; then ALLINONE_IMAGE_NAMES="lowcoderorg/lowcoder-ce:latest,${ALLINONE_IMAGE_NAMES}" FRONTEND_IMAGE_NAMES="lowcoderorg/lowcoder-ce-frontend:latest,${FRONTEND_IMAGE_NAMES}" + FRONTEND_EE_IMAGE_NAMES="lowcoderorg/lowcoder-ee-frontend:latest,${FRONTEND_EE_IMAGE_NAMES}" APISERVICE_IMAGE_NAMES="lowcoderorg/lowcoder-ce-api-service:latest,${APISERVICE_IMAGE_NAMES}" NODESERVICE_IMAGE_NAMES="lowcoderorg/lowcoder-ce-node-service:latest,${NODESERVICE_IMAGE_NAMES}" fi; echo "ALLINONE_IMAGE_NAMES=${ALLINONE_IMAGE_NAMES}" >> "${GITHUB_ENV}" echo "FRONTEND_IMAGE_NAMES=${FRONTEND_IMAGE_NAMES}" >> "${GITHUB_ENV}" + echo "FRONTEND_EE_IMAGE_NAMES=${FRONTEND_EE_IMAGE_NAMES}" >> "${GITHUB_ENV}" echo "APISERVICE_IMAGE_NAMES=${APISERVICE_IMAGE_NAMES}" >> "${GITHUB_ENV}" echo "NODESERVICE_IMAGE_NAMES=${NODESERVICE_IMAGE_NAMES}" >> "${GITHUB_ENV}" @@ -153,7 +156,7 @@ jobs: NODE_ENV: production with: file: ./deploy/docker/Dockerfile - target: lowcoder-ce-frontend + target: lowcoder-ee-frontend build-args: | REACT_APP_ENV=production REACT_APP_EDITION=enterprise @@ -162,7 +165,7 @@ jobs: linux/amd64 linux/arm64 push: true - tags: ${{ env.FRONTEND_IMAGE_NAMES }} + tags: ${{ env.FRONTEND_EE_IMAGE_NAMES }} - name: Build and push the node service image if: ${{ env.BUILD_NODESERVICE == 'true' }} From 511f79ff13e101c374a7187147a6c863468f099b Mon Sep 17 00:00:00 2001 From: RAHEEL Date: Wed, 2 Jul 2025 17:34:21 +0500 Subject: [PATCH 05/11] update env variable --- deploy/docker/Dockerfile | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/deploy/docker/Dockerfile b/deploy/docker/Dockerfile index 2c26de959..611bad508 100644 --- a/deploy/docker/Dockerfile +++ b/deploy/docker/Dockerfile @@ -200,7 +200,7 @@ RUN yarn --immutable ARG REACT_APP_COMMIT_ID=test ARG REACT_APP_ENV=production -ARG REACT_APP_EDITION=community +ARG REACT_APP_EDITION=enterprise ARG REACT_APP_DISABLE_JS_SANDBOX=true RUN yarn build:ee From bdcae5a11fd864297bb4e38c2ffac8ad3437cdec Mon Sep 17 00:00:00 2001 From: Falk Wolsky Date: Wed, 2 Jul 2025 14:44:57 +0200 Subject: [PATCH 06/11] Update docker-images.yml --- .github/workflows/docker-images.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/docker-images.yml b/.github/workflows/docker-images.yml index e26af7636..9132a02c5 100644 --- a/.github/workflows/docker-images.yml +++ b/.github/workflows/docker-images.yml @@ -156,7 +156,7 @@ jobs: NODE_ENV: production with: file: ./deploy/docker/Dockerfile - target: lowcoder-ee-frontend + target: lowcoder-enterprise-frontend build-args: | REACT_APP_ENV=production REACT_APP_EDITION=enterprise From c16d1a4b366ceeed455a721beb03bd8e58abe7c5 Mon Sep 17 00:00:00 2001 From: Falk Wolsky Date: Wed, 2 Jul 2025 15:15:21 +0200 Subject: [PATCH 07/11] Update docker-images.yml --- .github/workflows/docker-images.yml | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/.github/workflows/docker-images.yml b/.github/workflows/docker-images.yml index 9132a02c5..439280b43 100644 --- a/.github/workflows/docker-images.yml +++ b/.github/workflows/docker-images.yml @@ -72,22 +72,22 @@ jobs: fi; # Control which images to build - echo "BUILD_ALLINONE=${{ inputs.build_allinone || true }}" >> "${GITHUB_ENV}" - echo "BUILD_FRONTEND=${{ inputs.build_frontend || true }}" >> "${GITHUB_ENV}" - echo "BUILD_NODESERVICE=${{ inputs.build_nodeservice || true }}" >> "${GITHUB_ENV}" - echo "BUILD_APISERVICE=${{ inputs.build_apiservice || true }}" >> "${GITHUB_ENV}" + echo "BUILD_ALLINONE=${{ inputs.build_allinone || false }}" >> "${GITHUB_ENV}" + echo "BUILD_FRONTEND=${{ inputs.build_frontend || false }}" >> "${GITHUB_ENV}" + echo "BUILD_NODESERVICE=${{ inputs.build_nodeservice || false }}" >> "${GITHUB_ENV}" + echo "BUILD_APISERVICE=${{ inputs.build_apiservice || false }}" >> "${GITHUB_ENV}" # Image names ALLINONE_IMAGE_NAMES=lowcoderorg/lowcoder-ce:${IMAGE_TAG} FRONTEND_IMAGE_NAMES=lowcoderorg/lowcoder-ce-frontend:${IMAGE_TAG} - FRONTEND_EE_IMAGE_NAMES=lowcoderorg/lowcoder-ee-frontend:${IMAGE_TAG} + FRONTEND_EE_IMAGE_NAMES=lowcoderorg/lowcoder-enterprise-frontend:${IMAGE_TAG} APISERVICE_IMAGE_NAMES=lowcoderorg/lowcoder-ce-api-service:${IMAGE_TAG} NODESERVICE_IMAGE_NAMES=lowcoderorg/lowcoder-ce-node-service:${IMAGE_TAG} if [[ "${IS_LATEST}" == "true" ]]; then ALLINONE_IMAGE_NAMES="lowcoderorg/lowcoder-ce:latest,${ALLINONE_IMAGE_NAMES}" FRONTEND_IMAGE_NAMES="lowcoderorg/lowcoder-ce-frontend:latest,${FRONTEND_IMAGE_NAMES}" - FRONTEND_EE_IMAGE_NAMES="lowcoderorg/lowcoder-ee-frontend:latest,${FRONTEND_EE_IMAGE_NAMES}" + FRONTEND_EE_IMAGE_NAMES="lowcoderorg/lowcoder-enterprise-frontend:latest,${FRONTEND_EE_IMAGE_NAMES}" APISERVICE_IMAGE_NAMES="lowcoderorg/lowcoder-ce-api-service:latest,${APISERVICE_IMAGE_NAMES}" NODESERVICE_IMAGE_NAMES="lowcoderorg/lowcoder-ce-node-service:latest,${NODESERVICE_IMAGE_NAMES}" fi; From 42bb1104b8a797fe439ca8649f5418626d0c4f95 Mon Sep 17 00:00:00 2001 From: Falk Wolsky Date: Wed, 2 Jul 2025 15:40:53 +0200 Subject: [PATCH 08/11] Update Dockerfile --- deploy/docker/Dockerfile | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/deploy/docker/Dockerfile b/deploy/docker/Dockerfile index 611bad508..e94ca2fa3 100644 --- a/deploy/docker/Dockerfile +++ b/deploy/docker/Dockerfile @@ -225,7 +225,7 @@ RUN yarn build ## To create a separate image out of it, build it with: ## DOCKER_BUILDKIT=1 docker build -f deploy/docker/Dockerfile -t lowcoderorg/lowcoder-ee-frontend --target lowcoder-ee-frontend . ## -FROM nginx:1.27.1 AS lowcoder-ee-frontend +FROM nginx:1.27.1 AS lowcoder-enterprise-frontend LABEL maintainer="lowcoder" # Change default nginx user into lowcoder user and remove default nginx config From 145819a19a4032aefb235d1f62730f57f4eb86a1 Mon Sep 17 00:00:00 2001 From: Falk Wolsky Date: Thu, 24 Jul 2025 16:40:24 +0200 Subject: [PATCH 09/11] Update docker-images.yml --- .github/workflows/docker-images.yml | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/.github/workflows/docker-images.yml b/.github/workflows/docker-images.yml index 439280b43..be06cf1a4 100644 --- a/.github/workflows/docker-images.yml +++ b/.github/workflows/docker-images.yml @@ -72,10 +72,10 @@ jobs: fi; # Control which images to build - echo "BUILD_ALLINONE=${{ inputs.build_allinone || false }}" >> "${GITHUB_ENV}" - echo "BUILD_FRONTEND=${{ inputs.build_frontend || false }}" >> "${GITHUB_ENV}" - echo "BUILD_NODESERVICE=${{ inputs.build_nodeservice || false }}" >> "${GITHUB_ENV}" - echo "BUILD_APISERVICE=${{ inputs.build_apiservice || false }}" >> "${GITHUB_ENV}" + echo "BUILD_ALLINONE=${{ inputs.build_allinone || true }}" >> "${GITHUB_ENV}" + echo "BUILD_FRONTEND=${{ inputs.build_frontend || true }}" >> "${GITHUB_ENV}" + echo "BUILD_NODESERVICE=${{ inputs.build_nodeservice || true }}" >> "${GITHUB_ENV}" + echo "BUILD_APISERVICE=${{ inputs.build_apiservice || true }}" >> "${GITHUB_ENV}" # Image names ALLINONE_IMAGE_NAMES=lowcoderorg/lowcoder-ce:${IMAGE_TAG} From 27b31d5c799a89d73007a7db00ca4f40938d72d1 Mon Sep 17 00:00:00 2001 From: Faran Javed Date: Mon, 28 Jul 2025 20:35:57 +0500 Subject: [PATCH 10/11] [Feat]: #1820 add search filter/sorting for columns gui query --- .../queries/sqlQuery/columnNameDropdown.tsx | 46 +++++++++++++++---- 1 file changed, 38 insertions(+), 8 deletions(-) diff --git a/client/packages/lowcoder/src/comps/queries/sqlQuery/columnNameDropdown.tsx b/client/packages/lowcoder/src/comps/queries/sqlQuery/columnNameDropdown.tsx index e33be20bd..d79c00a78 100644 --- a/client/packages/lowcoder/src/comps/queries/sqlQuery/columnNameDropdown.tsx +++ b/client/packages/lowcoder/src/comps/queries/sqlQuery/columnNameDropdown.tsx @@ -1,6 +1,6 @@ import { DispatchType } from "lowcoder-core"; import { ControlPlacement } from "../../controls/controlParams"; -import React, { useContext } from "react"; +import React, { useContext, useState, useEffect } from "react"; import { Dropdown, OptionsType } from "lowcoder-design"; import { isEmpty, values } from "lodash"; import { useSelector } from "react-redux"; @@ -8,6 +8,8 @@ import { getDataSourceStructures } from "../../../redux/selectors/datasourceSele import { changeValueAction } from "lowcoder-core"; import { QueryContext } from "../../../util/context/QueryContext"; +const COLUMN_SORT_KEY = "lowcoder_column_sort"; + export const ColumnNameDropdown = (props: { table: string; value: string; @@ -18,13 +20,27 @@ export const ColumnNameDropdown = (props: { }) => { const context = useContext(QueryContext); const datasourceId = context?.datasourceId ?? ""; - const columns: OptionsType = - values(useSelector(getDataSourceStructures)[datasourceId]) - ?.find((t) => t.name === props.table) - ?.columns.map((column) => ({ - label: column.name, - value: column.name, - })) ?? []; + + // Simple sort preference from localStorage + const [sortColumns, setSortColumns] = useState(() => { + return localStorage.getItem(COLUMN_SORT_KEY) === 'true'; + }); + + useEffect(() => { + localStorage.setItem(COLUMN_SORT_KEY, sortColumns.toString()); + }, [sortColumns]); + + const rawColumns = values(useSelector(getDataSourceStructures)[datasourceId]) + ?.find((t) => t.name === props.table) + ?.columns.map((column) => ({ + label: column.name, + value: column.name, + })) ?? []; + + const columns: OptionsType = sortColumns + ? [...rawColumns].sort((a, b) => a.label.localeCompare(b.label)) + : rawColumns; + return ( ( +
+ +
+ )} /> ); }; From 168b3d439c775c3bb2ace8a4201b5f50a69296c2 Mon Sep 17 00:00:00 2001 From: Sanjai Kumar Date: Tue, 18 Nov 2025 11:43:09 +0530 Subject: [PATCH 11/11] feat: Add SCIM + SSO JIT provisioning support for Entra ID feat: Add SCIM + SSO JIT provisioning support for Entra ID feat: Add SCIM + SSO JIT provisioning support for Entra ID - Fix NullPointerException in createSCIMUserAndAddToOrg endpoint - Support placeholder user creation via SCIM for compliance - Enable JIT provisioning on first SSO login with correct auth source - Add comprehensive documentation for Entra ID integration - Prevent auth source mismatch between SCIM and SSO authentication This allows organizations to use SCIM for user pre-provisioning while maintaining proper SSO authentication with Entra ID (Azure AD). --- .../JIT_PROVISIONING_WITH_ENTRA_ID.md | 266 ++++++++++++++++++ .../self-hosting/JIT_QUICK_START.md | 80 ++++++ .../api/usermanagement/UserController.java | 38 +++ .../api/usermanagement/UserEndpoints.java | 9 + 4 files changed, 393 insertions(+) create mode 100644 docs/setup-and-run/self-hosting/JIT_PROVISIONING_WITH_ENTRA_ID.md create mode 100644 docs/setup-and-run/self-hosting/JIT_QUICK_START.md diff --git a/docs/setup-and-run/self-hosting/JIT_PROVISIONING_WITH_ENTRA_ID.md b/docs/setup-and-run/self-hosting/JIT_PROVISIONING_WITH_ENTRA_ID.md new file mode 100644 index 000000000..355547bc8 --- /dev/null +++ b/docs/setup-and-run/self-hosting/JIT_PROVISIONING_WITH_ENTRA_ID.md @@ -0,0 +1,266 @@ +# JIT (Just-In-Time) Provisioning with Entra ID (Azure AD) + +This guide explains how to set up JIT user provisioning with Microsoft Entra ID (formerly Azure AD) for Lowcoder. + +## Overview + +**JIT Provisioning** automatically creates user accounts in Lowcoder when users authenticate via SSO for the first time. This eliminates the need for manual user creation or SCIM provisioning for basic SSO scenarios. + +## How It Works + +1. User clicks **"Sign in with Entra ID"** on Lowcoder login page +2. User authenticates with Microsoft Entra ID +3. Entra ID redirects back to Lowcoder with authentication details +4. Lowcoder automatically: + - Creates a new user account (if doesn't exist) + - Sets up the correct auth connection (Entra ID, not EMAIL) + - Adds user to the organization + - Logs user in + +## Prerequisites + +- Lowcoder instance running (self-hosted or cloud) +- Admin access to Lowcoder +- Microsoft Entra ID (Azure AD) tenant +- Admin access to Azure Portal + +## Step 1: Configure Application in Azure Portal + +### 1.1 Register a New Application + +1. Go to [Azure Portal](https://portal.azure.com) +2. Navigate to **Azure Active Directory** → **App registrations** +3. Click **New registration** +4. Configure: + - **Name**: `Lowcoder SSO` + - **Supported account types**: Choose based on your needs + - Single tenant (most common for enterprise) + - Multi-tenant (if needed) + - **Redirect URI**: + - Platform: `Web` + - URI: `https://your-lowcoder-domain.com/api/auth/oauth2/callback` +5. Click **Register** + +### 1.2 Configure API Permissions + +1. In your app registration, go to **API permissions** +2. Click **Add a permission** +3. Select **Microsoft Graph** +4. Choose **Delegated permissions** +5. Add these permissions: + - `openid` + - `profile` + - `email` + - `User.Read` +6. Click **Add permissions** +7. Click **Grant admin consent** (if you have admin rights) + +### 1.3 Create Client Secret + +1. Go to **Certificates & secrets** +2. Click **New client secret** +3. Description: `Lowcoder SSO Secret` +4. Expires: Choose appropriate duration (12-24 months recommended) +5. Click **Add** +6. **IMPORTANT**: Copy the secret **Value** immediately (you won't see it again!) + +### 1.4 Note Your Configuration Details + +From **Overview** page, note: + +- **Application (client) ID**: `xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx` +- **Directory (tenant) ID**: `xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx` + +## Step 2: Configure Entra ID Provider in Lowcoder + +### 2.1 Access Admin Settings + +1. Log in to Lowcoder as **admin** +2. Go to **Settings** → **Authentication** (or Admin panel) +3. Click **Add Authentication Provider** + +### 2.2 Configure Generic OAuth Provider + +Fill in the following details: + +| Field | Value | +| -------------------------- | --------------------------------------------------------------------- | +| **Provider Name** | `Entra ID` or `Azure AD` | +| **Auth Type** | `GENERIC` (Generic OAuth2) | +| **Client ID** | `` | +| **Client Secret** | `` | +| **Authorization Endpoint** | `https://login.microsoftonline.com/{tenant-id}/oauth2/v2.0/authorize` | +| **Token Endpoint** | `https://login.microsoftonline.com/{tenant-id}/oauth2/v2.0/token` | +| **User Info Endpoint** | `https://graph.microsoft.com/v1.0/me` | +| **Scope** | `openid profile email User.Read` | +| **Enable Registration** | ✅ **TRUE** (This enables JIT provisioning!) | + +Replace `{tenant-id}` with your actual tenant ID. + +### 2.3 Configure User Attribute Mappings + +Set up how Entra ID user attributes map to Lowcoder user fields: + +```json +{ + "uid": "id", + "username": "userPrincipalName", + "email": "mail", + "avatar": "photo" +} +``` + +Or if email field is sometimes null: + +```json +{ + "uid": "id", + "username": "userPrincipalName", + "email": "userPrincipalName", + "avatar": "photo" +} +``` + +### 2.4 Save Configuration + +Click **Save** or **Enable** to activate the provider. + +## Step 3: Test JIT Provisioning + +### 3.1 Test User Login + +1. **Log out** from Lowcoder (if logged in) +2. Go to Lowcoder login page +3. You should see **"Sign in with Entra ID"** button +4. Click the button +5. You'll be redirected to Microsoft login +6. Enter credentials and authenticate +7. You'll be redirected back to Lowcoder +8. **User account is automatically created** ✅ + +### 3.2 Verify User Creation + +As admin: + +1. Go to **Settings** → **Members** or **Users** +2. You should see the newly created user +3. Check user details: + - Auth Source should be **"Entra ID"** (not EMAIL) + - Email should match the Entra ID account + - User should be active + +## SCIM + JIT Combined Approach (Optional) + +If you need both SCIM provisioning AND SSO authentication: + +### Use Case + +- SCIM pre-provisions users (for compliance/audit) +- Users still authenticate via SSO +- JIT adds auth connection on first login + +### How It Works with Updated Code + +The updated `createSCIMUserAndAddToOrg` endpoint now: + +1. **Creates a placeholder user** via SCIM (no auth connection) +2. **On first SSO login**, JIT provisioning adds the Entra ID auth connection +3. **Subsequent logins** work normally via SSO + +### Configure Entra ID SCIM Provisioning + +1. In Azure Portal, go to your **Enterprise Application** +2. Navigate to **Provisioning** +3. Set **Provisioning Mode**: `Automatic` +4. Configure: + - **Tenant URL**: `https://your-lowcoder-domain.com/api/v1/organizations/{orgId}/scim/users` + - **Secret Token**: Your API token +5. **Mappings**: Map Azure AD attributes to SCIM attributes +6. **Save** and **Start Provisioning** + +## Troubleshooting + +### Issue: "Sign in with Entra ID" button doesn't appear + +**Solution**: + +- Verify the auth provider is **enabled** in settings +- Check that `enableRegister` is set to `true` +- Clear browser cache and reload + +### Issue: User login fails with "LOG_IN_SOURCE_NOT_SUPPORTED" + +**Solution**: + +- Verify Authorization, Token, and User Info endpoints are correct +- Check that tenant ID is properly replaced in URLs +- Verify client ID and secret are correct + +### Issue: User created but with wrong auth source (EMAIL instead of Entra ID) + +**Solution**: + +- This means SCIM endpoint was used with old code +- With updated code, SCIM creates placeholder users +- JIT adds correct auth connection on first SSO login +- Rebuild Docker image with the fix + +### Issue: "EMAIL_PROVIDER_DISABLED" error + +**Solution**: + +- This error occurs if trying to create EMAIL-based users when EMAIL auth is disabled +- Use SSO with JIT provisioning instead +- Don't use the old SCIM implementation that creates EMAIL users + +## Security Considerations + +1. **Client Secret**: Store securely, rotate regularly +2. **Redirect URI**: Must exactly match what's configured in Azure +3. **Scope**: Request minimum permissions needed +4. **Enable Registration**: Only enable if you want JIT provisioning +5. **Organization**: Configure which organization new users join + +## Advanced Configuration + +### Multi-Organization Support + +If you have multiple organizations: + +- Configure separate OAuth providers per organization +- Or use organization domains to auto-assign users + +### Custom User Roles + +To assign custom roles on JIT provisioning: + +- Modify `AuthenticationApiServiceImpl.onUserLogin()` +- Add logic to check Entra ID groups/roles +- Map to Lowcoder roles accordingly + +### Email Verification + +JIT-provisioned users are automatically verified since they authenticated via SSO. + +## Comparison: SCIM vs JIT + +| Feature | SCIM Provisioning | JIT Provisioning | +| -------------------- | ---------------------------- | ---------------------- | +| **User Creation** | Pre-provisioned via SCIM | Created on first login | +| **Setup Complexity** | High | Low | +| **Auth Source** | EMAIL (with old code) | Correct SSO source | +| **Works with SSO** | Requires fix | ✅ Native support | +| **Audit Trail** | Full provisioning audit | Login-based audit | +| **Use Case** | Compliance, pre-provisioning | Simple SSO | + +## Recommendation + +For most use cases: **Use JIT Provisioning** (simpler, works correctly with SSO) + +For compliance requirements: **Use SCIM + JIT Combined** (with updated code) + +## References + +- [Microsoft Entra ID OAuth2 Documentation](https://learn.microsoft.com/en-us/azure/active-directory/develop/v2-oauth2-auth-code-flow) +- [Lowcoder Authentication Documentation](../../../workspaces-and-teamwork/oauth/) +- [Generic OAuth Provider Setup](../../../workspaces-and-teamwork/oauth/generic-oauth-provider.md) diff --git a/docs/setup-and-run/self-hosting/JIT_QUICK_START.md b/docs/setup-and-run/self-hosting/JIT_QUICK_START.md new file mode 100644 index 000000000..ab4a4a1f0 --- /dev/null +++ b/docs/setup-and-run/self-hosting/JIT_QUICK_START.md @@ -0,0 +1,80 @@ +# Quick Start: Enable JIT Provisioning with Entra ID + +## What is JIT Provisioning? + +**Just-In-Time (JIT) Provisioning** automatically creates user accounts when they first login via SSO. No manual user creation or SCIM needed! + +## Why Use JIT? + +✅ **Simple** - Minimal configuration +✅ **Secure** - Users authenticated via Entra ID +✅ **Correct** - Proper auth source (not EMAIL) +✅ **Fast** - Users created on first login + +## 5-Minute Setup + +### 1. Azure Portal Setup (3 minutes) + +1. Go to [Azure Portal](https://portal.azure.com) → **App registrations** → **New registration** +2. Name: `Lowcoder SSO`, Redirect URI: `https://your-domain.com/api/auth/oauth2/callback` +3. Create a **Client Secret** (save the value!) +4. Add permissions: **API permissions** → **Microsoft Graph** → `User.Read`, `email`, `profile`, `openid` +5. Note your **Client ID** and **Tenant ID** + +### 2. Lowcoder Configuration (2 minutes) + +Login as admin → **Settings** → **Authentication** → **Add Provider**: + +```yaml +Provider Name: Entra ID +Auth Type: GENERIC +Client ID: +Client Secret: +Authorization Endpoint: https://login.microsoftonline.com/{tenant-id}/oauth2/v2.0/authorize +Token Endpoint: https://login.microsoftonline.com/{tenant-id}/oauth2/v2.0/token +User Info Endpoint: https://graph.microsoft.com/v1.0/me +Scope: openid profile email User.Read +Enable Registration: ✅ TRUE # ← This enables JIT! +``` + +**User Mappings**: + +```json +{ + "uid": "id", + "username": "userPrincipalName", + "email": "mail" +} +``` + +### 3. Test (30 seconds) + +1. Logout from Lowcoder +2. Click **"Sign in with Entra ID"** +3. Authenticate with Microsoft +4. ✅ User automatically created and logged in! + +## That's It! + +No SCIM needed. No manual user creation. Users are provisioned automatically on first login with the correct authentication source. + +## Need SCIM? + +If you need SCIM for compliance, see the full guide: [JIT_PROVISIONING_WITH_ENTRA_ID.md](./JIT_PROVISIONING_WITH_ENTRA_ID.md) + +## Troubleshooting + +**Can't see "Sign in with Entra ID" button?** + +- Check `Enable Registration` is ✅ TRUE +- Verify provider is enabled in settings + +**Login fails?** + +- Verify redirect URI matches exactly +- Check client ID and secret are correct +- Verify tenant ID in endpoints + +## Support + +For detailed documentation, see: [JIT_PROVISIONING_WITH_ENTRA_ID.md](./JIT_PROVISIONING_WITH_ENTRA_ID.md) diff --git a/server/api-service/lowcoder-server/src/main/java/org/lowcoder/api/usermanagement/UserController.java b/server/api-service/lowcoder-server/src/main/java/org/lowcoder/api/usermanagement/UserController.java index 0cc4d89df..f6bb280a0 100644 --- a/server/api-service/lowcoder-server/src/main/java/org/lowcoder/api/usermanagement/UserController.java +++ b/server/api-service/lowcoder-server/src/main/java/org/lowcoder/api/usermanagement/UserController.java @@ -18,6 +18,7 @@ import org.lowcoder.domain.organization.service.OrgMemberService; import org.lowcoder.domain.organization.service.OrganizationService; import org.lowcoder.domain.user.constant.UserStatusType; +import org.lowcoder.domain.user.model.AuthUser; import org.lowcoder.domain.user.model.User; import org.lowcoder.domain.user.model.UserDetail; import org.lowcoder.domain.user.service.UserService; @@ -25,6 +26,7 @@ import org.lowcoder.sdk.config.CommonConfig; import org.lowcoder.sdk.constants.AuthSourceConstants; import org.lowcoder.sdk.exception.BizError; +import org.lowcoder.sdk.exception.BizException; import org.springframework.data.domain.Sort; import org.springframework.http.HttpStatus; import org.springframework.http.codec.multipart.Part; @@ -39,6 +41,7 @@ import java.util.HashMap; import java.util.List; import java.util.Map; +import java.util.HashSet; import java.util.stream.Collectors; import static org.lowcoder.sdk.exception.BizError.INVALID_USER_STATUS; @@ -70,6 +73,41 @@ public Mono> createUserAndAddToOrg(@PathVariable String orgId, C .map(ResponseView::success); } + @Override + public Mono> createSCIMUserAndAddToOrg(@PathVariable String orgId, CreateUserRequest request) { + return orgApiService.checkVisitorAdminRole(orgId) + .flatMap(__ -> { + // For SCIM provisioning: Create a minimal user without auth connection + // The auth connection will be added on first SSO login via JIT provisioning + // This allows SCIM to pre-provision users while SSO handles authentication + + // Check if user already exists by email + return userService.findByEmailDeep(request.email()) + .flatMap(existingUser -> { + // User already exists, just add to org if not already a member + return orgMemberService.tryAddOrgMember(orgId, existingUser.getId(), MemberRole.MEMBER) + .thenReturn(existingUser); + }) + .switchIfEmpty( + // Create new user without auth connection (placeholder for SSO) + Mono.defer(() -> { + User newUser = User.builder() + .name(request.email()) + .email(request.email()) + .isEnabled(true) + .build(); + newUser.setConnections(new HashSet<>()); + newUser.setIsNewUser(true); + + return userService.saveUser(newUser) + .delayUntil(user -> orgMemberService.tryAddOrgMember(orgId, user.getId(), MemberRole.MEMBER)); + }) + ); + }) + .delayUntil(user -> orgApiService.switchCurrentOrganizationTo(user.getId(), orgId)) + .map(ResponseView::success); + } + @Override public Mono> getUserProfile(ServerWebExchange exchange) { return sessionUserService.getVisitor() diff --git a/server/api-service/lowcoder-server/src/main/java/org/lowcoder/api/usermanagement/UserEndpoints.java b/server/api-service/lowcoder-server/src/main/java/org/lowcoder/api/usermanagement/UserEndpoints.java index 955bb70bf..9ba473780 100644 --- a/server/api-service/lowcoder-server/src/main/java/org/lowcoder/api/usermanagement/UserEndpoints.java +++ b/server/api-service/lowcoder-server/src/main/java/org/lowcoder/api/usermanagement/UserEndpoints.java @@ -39,6 +39,15 @@ public interface UserEndpoints @PostMapping("/new/{orgId}") public Mono> createUserAndAddToOrg(@PathVariable String orgId, @RequestBody CreateUserRequest request); + +@Operation( + tags = TAG_USER_MANAGEMENT, + operationId = "createSCIMUserAndAddToOrg", + summary = "Create SCIM user and add to the org", + description = "Create a new user from Entra/Auth0 through SCIM add to the Organization." + ) + @PostMapping("/newscim/{orgId}") + public Mono> createSCIMUserAndAddToOrg(@PathVariable String orgId, @RequestBody CreateUserRequest request); @Operation( tags = TAG_USER_MANAGEMENT, operationId = "getUserProfile",