From 74cd890bddd7ef296572b5f7598727eedbc09984 Mon Sep 17 00:00:00 2001 From: Philip Okugbe <16838612+Philipinho@users.noreply.github.com> Date: Sun, 31 Aug 2025 20:33:37 +0100 Subject: [PATCH] feat(EE): implement SSO group sync for SAML and OIDC (#1452) * feat: implement SSO group synchronization for SAML and OIDC - Add group_sync column to auth_providers table - Extract groups from SAML attributes (memberOf, groups, roles) - Extract groups from OIDC claims (groups, roles) - Implement case-insensitive group matching with auto-creation - Sync user groups on each SSO login - Ensure only one provider can have group sync enabled at a time - Add group sync toggle to SAML and OIDC configuration forms * rename column --- .../src/ee/security/components/sso-oidc-form.tsx | 14 ++++++++++++++ .../src/ee/security/components/sso-saml-form.tsx | 14 ++++++++++++++ .../client/src/ee/security/types/security.types.ts | 1 + ...0831T191600-add-group-sync-to-auth-providers.ts | 8 +++----- apps/server/src/database/types/db.d.ts | 1 + apps/server/src/ee | 2 +- 6 files changed, 34 insertions(+), 6 deletions(-) diff --git a/apps/client/src/ee/security/components/sso-oidc-form.tsx b/apps/client/src/ee/security/components/sso-oidc-form.tsx index 28b1ac47..aa9e653c 100644 --- a/apps/client/src/ee/security/components/sso-oidc-form.tsx +++ b/apps/client/src/ee/security/components/sso-oidc-form.tsx @@ -16,6 +16,7 @@ const ssoSchema = z.object({ oidcClientSecret: z.string().min(1, "Client secret is required"), isEnabled: z.boolean(), allowSignup: z.boolean(), + groupSync: z.boolean(), }); type SSOFormValues = z.infer; @@ -36,6 +37,7 @@ export function SsoOIDCForm({ provider, onClose }: SsoFormProps) { oidcClientSecret: provider.oidcClientSecret || "", isEnabled: provider.isEnabled, allowSignup: provider.allowSignup, + groupSync: provider.groupSync || false, }, validate: zodResolver(ssoSchema), }); @@ -67,6 +69,9 @@ export function SsoOIDCForm({ provider, onClose }: SsoFormProps) { if (form.isDirty("allowSignup")) { ssoData.allowSignup = values.allowSignup; } + if (form.isDirty("groupSync")) { + ssoData.groupSync = values.groupSync; + } await updateSsoProviderMutation.mutateAsync(ssoData); form.resetDirty(); @@ -119,6 +124,15 @@ export function SsoOIDCForm({ provider, onClose }: SsoFormProps) { /> + +
{t("Group sync")}
+ +
+
{t("Enabled")}
; @@ -45,6 +46,7 @@ export function SsoSamlForm({ provider, onClose }: SsoFormProps) { samlCertificate: provider.samlCertificate || "", isEnabled: provider.isEnabled, allowSignup: provider.allowSignup, + groupSync: provider.groupSync || false, }, validate: zodResolver(ssoSchema), }); @@ -75,6 +77,9 @@ export function SsoSamlForm({ provider, onClose }: SsoFormProps) { if (form.isDirty("allowSignup")) { ssoData.allowSignup = values.allowSignup; } + if (form.isDirty("groupSync")) { + ssoData.groupSync = values.groupSync; + } await updateSsoProviderMutation.mutateAsync(ssoData); form.resetDirty(); @@ -132,6 +137,15 @@ export function SsoSamlForm({ provider, onClose }: SsoFormProps) { />
+ +
{t("Group sync")}
+ +
+
{t("Enabled")}
): Promise { await db.schema .alterTable('auth_providers') - .addColumn('is_group_sync_enabled', 'boolean', (col) => - col.defaultTo(false).notNull(), - ) + .addColumn('group_sync', 'boolean', (col) => col.defaultTo(false).notNull()) .execute(); } export async function down(db: Kysely): Promise { await db.schema .alterTable('auth_providers') - .dropColumn('is_group_sync_enabled') + .dropColumn('group_sync') .execute(); -} \ No newline at end of file +} diff --git a/apps/server/src/database/types/db.d.ts b/apps/server/src/database/types/db.d.ts index e8662649..ab0fa898 100644 --- a/apps/server/src/database/types/db.d.ts +++ b/apps/server/src/database/types/db.d.ts @@ -62,6 +62,7 @@ export interface AuthProviders { deletedAt: Timestamp | null; id: Generated; isEnabled: Generated; + groupSync: Generated; name: string; oidcClientId: string | null; oidcClientSecret: string | null; diff --git a/apps/server/src/ee b/apps/server/src/ee index fbc01d80..10519eea 160000 --- a/apps/server/src/ee +++ b/apps/server/src/ee @@ -1 +1 @@ -Subproject commit fbc01d808f3edd7d16a64c21251a0bcb720f1ba4 +Subproject commit 10519eeaa24881201f21f79fc5edc632fe2c185c