From b4471edff1a20a9bf3901d8f16c253402500dc2b Mon Sep 17 00:00:00 2001 From: Anthony Stirling <77850077+Frooodle@users.noreply.github.com> Date: Sat, 29 Nov 2025 11:47:37 +0000 Subject: [PATCH 1/3] sso --- .../controller/api/misc/ConfigController.java | 10 +++ .../controller/api/UserController.java | 17 ++++- .../public/locales/en-GB/translation.toml | 8 +++ .../src/core/contexts/AppConfigContext.tsx | 2 + .../components/shared/InviteMembersModal.tsx | 62 ++++++++++++++----- 5 files changed, 82 insertions(+), 17 deletions(-) diff --git a/app/core/src/main/java/stirling/software/SPDF/controller/api/misc/ConfigController.java b/app/core/src/main/java/stirling/software/SPDF/controller/api/misc/ConfigController.java index 4f31b83ef..b770a301b 100644 --- a/app/core/src/main/java/stirling/software/SPDF/controller/api/misc/ConfigController.java +++ b/app/core/src/main/java/stirling/software/SPDF/controller/api/misc/ConfigController.java @@ -83,6 +83,16 @@ public class ConfigController { applicationProperties.getSecurity().getEnableLogin() && userService != null; configData.put("enableLogin", enableLogin); + // SSO Provider settings + boolean enableOAuth = + applicationProperties.getSecurity().getOAUTH2() != null + && applicationProperties.getSecurity().getOAUTH2().getEnabled(); + boolean enableSaml = + applicationProperties.getSecurity().getSAML2() != null + && applicationProperties.getSecurity().getSAML2().getEnabled(); + configData.put("enableOAuth", enableOAuth); + configData.put("enableSaml", enableSaml); + // Mail settings - check both SMTP enabled AND invites enabled boolean smtpEnabled = applicationProperties.getMail().isEnabled(); boolean invitesEnabled = applicationProperties.getMail().isEnableInvites(); diff --git a/app/proprietary/src/main/java/stirling/software/proprietary/security/controller/api/UserController.java b/app/proprietary/src/main/java/stirling/software/proprietary/security/controller/api/UserController.java index 9e31ee69c..85e9c4005 100644 --- a/app/proprietary/src/main/java/stirling/software/proprietary/security/controller/api/UserController.java +++ b/app/proprietary/src/main/java/stirling/software/proprietary/security/controller/api/UserController.java @@ -386,9 +386,22 @@ public class UserController { } } - if (authType.equalsIgnoreCase(AuthenticationType.SSO.toString())) { - userService.saveUser(username, AuthenticationType.SSO, effectiveTeamId, role); + // Parse authentication type + AuthenticationType authenticationType; + try { + authenticationType = AuthenticationType.valueOf(authType.toUpperCase()); + } catch (IllegalArgumentException e) { + return ResponseEntity.status(HttpStatus.BAD_REQUEST) + .body(Map.of("error", "Invalid authentication type specified.")); + } + + // For SSO types (SSO, OAUTH2, SAML2), password is not required + if (authenticationType == AuthenticationType.SSO + || authenticationType == AuthenticationType.OAUTH2 + || authenticationType == AuthenticationType.SAML2) { + userService.saveUser(username, authenticationType, effectiveTeamId, role); } else { + // For WEB auth type, password is required if (password == null || password.isBlank()) { return ResponseEntity.status(HttpStatus.BAD_REQUEST) .body(Map.of("error", "Password is required.")); diff --git a/frontend/public/locales/en-GB/translation.toml b/frontend/public/locales/en-GB/translation.toml index d1ef09e6f..c4df8e900 100644 --- a/frontend/public/locales/en-GB/translation.toml +++ b/frontend/public/locales/en-GB/translation.toml @@ -5223,9 +5223,11 @@ username = "Username (Email)" usernamePlaceholder = "user@example.com" password = "Password" passwordPlaceholder = "Enter password" +passwordRequired = "Password is required" role = "Role" team = "Team (Optional)" teamPlaceholder = "Select a team" +authType = "Authentication Type" forcePasswordChange = "Force password change on first login" cancel = "Cancel" submit = "Add Member" @@ -5234,6 +5236,12 @@ passwordTooShort = "Password must be at least 6 characters" success = "User created successfully" error = "Failed to create user" +[workspace.people.authType] +password = "Password" +oauth = "OAuth2" +saml = "SAML2" +ssoDescription = "User will authenticate via SSO provider" + [workspace.people.editMember] title = "Edit Member" editing = "Editing:" diff --git a/frontend/src/core/contexts/AppConfigContext.tsx b/frontend/src/core/contexts/AppConfigContext.tsx index 5ae0d30a6..4c6e425eb 100644 --- a/frontend/src/core/contexts/AppConfigContext.tsx +++ b/frontend/src/core/contexts/AppConfigContext.tsx @@ -23,6 +23,8 @@ export interface AppConfig { logoStyle?: 'modern' | 'classic'; enableLogin?: boolean; enableEmailInvites?: boolean; + enableOAuth?: boolean; + enableSaml?: boolean; isAdmin?: boolean; enableAlphaFunctionality?: boolean; enableAnalytics?: boolean | null; diff --git a/frontend/src/proprietary/components/shared/InviteMembersModal.tsx b/frontend/src/proprietary/components/shared/InviteMembersModal.tsx index 9a13d18a2..0570db9a8 100644 --- a/frontend/src/proprietary/components/shared/InviteMembersModal.tsx +++ b/frontend/src/proprietary/components/shared/InviteMembersModal.tsx @@ -55,6 +55,7 @@ export default function InviteMembersModal({ opened, onClose }: InviteMembersMod password: '', role: 'ROLE_USER', teamId: undefined as number | undefined, + authType: 'WEB' as 'WEB' | 'OAUTH2' | 'SAML2', forceChange: false, }); @@ -119,11 +120,17 @@ export default function InviteMembersModal({ opened, onClose }: InviteMembersMod })); const handleInviteUser = async () => { - if (!inviteForm.username || !inviteForm.password) { + if (!inviteForm.username) { alert({ alertType: 'error', title: t('workspace.people.addMember.usernameRequired') }); return; } + // Password is only required for WEB auth type + if (inviteForm.authType === 'WEB' && !inviteForm.password) { + alert({ alertType: 'error', title: t('workspace.people.addMember.passwordRequired', 'Password is required') }); + return; + } + try { setProcessing(true); await userManagementService.createUser({ @@ -131,7 +138,7 @@ export default function InviteMembersModal({ opened, onClose }: InviteMembersMod password: inviteForm.password, role: inviteForm.role, teamId: inviteForm.teamId, - authType: 'password', + authType: inviteForm.authType.toLowerCase(), forceChange: inviteForm.forceChange, }); alert({ alertType: 'success', title: t('workspace.people.addMember.success') }); @@ -142,6 +149,7 @@ export default function InviteMembersModal({ opened, onClose }: InviteMembersMod password: '', role: 'ROLE_USER', teamId: undefined, + authType: 'WEB', forceChange: false, }); } catch (error: any) { @@ -228,6 +236,7 @@ export default function InviteMembersModal({ opened, onClose }: InviteMembersMod password: '', role: 'ROLE_USER', teamId: undefined, + authType: 'WEB', forceChange: false, }); setEmailInviteForm({ @@ -486,14 +495,42 @@ export default function InviteMembersModal({ opened, onClose }: InviteMembersMod onChange={(e) => setInviteForm({ ...inviteForm, username: e.currentTarget.value })} required /> - setInviteForm({ ...inviteForm, password: e.currentTarget.value })} - required - /> + + {/* Auth Type Selector - only show if SSO is enabled */} + {(config?.enableOAuth || config?.enableSaml) && ( + - setInviteForm({ ...inviteForm, forceChange: e.currentTarget.checked })} - /> )} From 856dd2ed07eef5fc33f97d1ae3c632ed73709105 Mon Sep 17 00:00:00 2001 From: Anthony Stirling <77850077+Frooodle@users.noreply.github.com> Date: Sat, 29 Nov 2025 11:59:13 +0000 Subject: [PATCH 2/3] clean --- .../src/proprietary/components/shared/InviteMembersModal.tsx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/frontend/src/proprietary/components/shared/InviteMembersModal.tsx b/frontend/src/proprietary/components/shared/InviteMembersModal.tsx index 0570db9a8..9fa72e0c0 100644 --- a/frontend/src/proprietary/components/shared/InviteMembersModal.tsx +++ b/frontend/src/proprietary/components/shared/InviteMembersModal.tsx @@ -138,7 +138,7 @@ export default function InviteMembersModal({ opened, onClose }: InviteMembersMod password: inviteForm.password, role: inviteForm.role, teamId: inviteForm.teamId, - authType: inviteForm.authType.toLowerCase(), + authType: inviteForm.authType === 'WEB' ? 'password' : 'SSO', forceChange: inviteForm.forceChange, }); alert({ alertType: 'success', title: t('workspace.people.addMember.success') }); From 716a5d8d652c77e97f91a43dbb0b4d0a7a75e15a Mon Sep 17 00:00:00 2001 From: Anthony Stirling <77850077+Frooodle@users.noreply.github.com> Date: Sat, 29 Nov 2025 12:05:38 +0000 Subject: [PATCH 3/3] lombok --- .../SPDF/controller/api/misc/ConfigController.java | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/app/core/src/main/java/stirling/software/SPDF/controller/api/misc/ConfigController.java b/app/core/src/main/java/stirling/software/SPDF/controller/api/misc/ConfigController.java index b770a301b..a82a55a78 100644 --- a/app/core/src/main/java/stirling/software/SPDF/controller/api/misc/ConfigController.java +++ b/app/core/src/main/java/stirling/software/SPDF/controller/api/misc/ConfigController.java @@ -85,11 +85,11 @@ public class ConfigController { // SSO Provider settings boolean enableOAuth = - applicationProperties.getSecurity().getOAUTH2() != null - && applicationProperties.getSecurity().getOAUTH2().getEnabled(); + applicationProperties.getSecurity().getOauth2() != null + && applicationProperties.getSecurity().getOauth2().getEnabled(); boolean enableSaml = - applicationProperties.getSecurity().getSAML2() != null - && applicationProperties.getSecurity().getSAML2().getEnabled(); + applicationProperties.getSecurity().getSaml2() != null + && applicationProperties.getSecurity().getSaml2().getEnabled(); configData.put("enableOAuth", enableOAuth); configData.put("enableSaml", enableSaml);