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 2f8d4b62b..6a2044c64 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,6 +85,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 6a18aeff0..dcd916498 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 @@ -390,9 +390,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 5d9f08467..6d3a7c143 100644 --- a/frontend/public/locales/en-GB/translation.toml +++ b/frontend/public/locales/en-GB/translation.toml @@ -5477,9 +5477,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" @@ -5488,6 +5490,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 c8fcdf64c..6a4d70d88 100644 --- a/frontend/src/core/contexts/AppConfigContext.tsx +++ b/frontend/src/core/contexts/AppConfigContext.tsx @@ -24,6 +24,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 f583f3cb7..678ae7e39 100644 --- a/frontend/src/proprietary/components/shared/InviteMembersModal.tsx +++ b/frontend/src/proprietary/components/shared/InviteMembersModal.tsx @@ -56,6 +56,7 @@ export default function InviteMembersModal({ opened, onClose, onSuccess }: Invit password: '', role: 'ROLE_USER', teamId: undefined as number | undefined, + authType: 'WEB' as 'WEB' | 'OAUTH2' | 'SAML2', forceChange: false, }); @@ -120,11 +121,17 @@ export default function InviteMembersModal({ opened, onClose, onSuccess }: Invit })); 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({ @@ -132,7 +139,7 @@ export default function InviteMembersModal({ opened, onClose, onSuccess }: Invit password: inviteForm.password, role: inviteForm.role, teamId: inviteForm.teamId, - authType: 'password', + authType: inviteForm.authType === 'WEB' ? 'password' : 'SSO', forceChange: inviteForm.forceChange, }); alert({ alertType: 'success', title: t('workspace.people.addMember.success') }); @@ -144,6 +151,7 @@ export default function InviteMembersModal({ opened, onClose, onSuccess }: Invit password: '', role: 'ROLE_USER', teamId: undefined, + authType: 'WEB', forceChange: false, }); } catch (error: any) { @@ -243,6 +251,7 @@ export default function InviteMembersModal({ opened, onClose, onSuccess }: Invit password: '', role: 'ROLE_USER', teamId: undefined, + authType: 'WEB', forceChange: false, }); setEmailInviteForm({ @@ -501,14 +510,42 @@ export default function InviteMembersModal({ opened, onClose, onSuccess }: Invit 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 })} - /> )}