This commit is contained in:
Anthony Stirling 2025-12-18 17:21:48 +00:00 committed by GitHub
commit b341ef4dbd
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
5 changed files with 82 additions and 17 deletions

View File

@ -85,6 +85,16 @@ public class ConfigController {
applicationProperties.getSecurity().getEnableLogin() && userService != null; applicationProperties.getSecurity().getEnableLogin() && userService != null;
configData.put("enableLogin", enableLogin); 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 // Mail settings - check both SMTP enabled AND invites enabled
boolean smtpEnabled = applicationProperties.getMail().isEnabled(); boolean smtpEnabled = applicationProperties.getMail().isEnabled();
boolean invitesEnabled = applicationProperties.getMail().isEnableInvites(); boolean invitesEnabled = applicationProperties.getMail().isEnableInvites();

View File

@ -390,9 +390,22 @@ public class UserController {
} }
} }
if (authType.equalsIgnoreCase(AuthenticationType.SSO.toString())) { // Parse authentication type
userService.saveUser(username, AuthenticationType.SSO, effectiveTeamId, role); 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 { } else {
// For WEB auth type, password is required
if (password == null || password.isBlank()) { if (password == null || password.isBlank()) {
return ResponseEntity.status(HttpStatus.BAD_REQUEST) return ResponseEntity.status(HttpStatus.BAD_REQUEST)
.body(Map.of("error", "Password is required.")); .body(Map.of("error", "Password is required."));

View File

@ -5477,9 +5477,11 @@ username = "Username (Email)"
usernamePlaceholder = "user@example.com" usernamePlaceholder = "user@example.com"
password = "Password" password = "Password"
passwordPlaceholder = "Enter password" passwordPlaceholder = "Enter password"
passwordRequired = "Password is required"
role = "Role" role = "Role"
team = "Team (Optional)" team = "Team (Optional)"
teamPlaceholder = "Select a team" teamPlaceholder = "Select a team"
authType = "Authentication Type"
forcePasswordChange = "Force password change on first login" forcePasswordChange = "Force password change on first login"
cancel = "Cancel" cancel = "Cancel"
submit = "Add Member" submit = "Add Member"
@ -5488,6 +5490,12 @@ passwordTooShort = "Password must be at least 6 characters"
success = "User created successfully" success = "User created successfully"
error = "Failed to create user" error = "Failed to create user"
[workspace.people.authType]
password = "Password"
oauth = "OAuth2"
saml = "SAML2"
ssoDescription = "User will authenticate via SSO provider"
[workspace.people.editMember] [workspace.people.editMember]
title = "Edit Member" title = "Edit Member"
editing = "Editing:" editing = "Editing:"

View File

@ -24,6 +24,8 @@ export interface AppConfig {
logoStyle?: 'modern' | 'classic'; logoStyle?: 'modern' | 'classic';
enableLogin?: boolean; enableLogin?: boolean;
enableEmailInvites?: boolean; enableEmailInvites?: boolean;
enableOAuth?: boolean;
enableSaml?: boolean;
isAdmin?: boolean; isAdmin?: boolean;
enableAlphaFunctionality?: boolean; enableAlphaFunctionality?: boolean;
enableAnalytics?: boolean | null; enableAnalytics?: boolean | null;

View File

@ -56,6 +56,7 @@ export default function InviteMembersModal({ opened, onClose, onSuccess }: Invit
password: '', password: '',
role: 'ROLE_USER', role: 'ROLE_USER',
teamId: undefined as number | undefined, teamId: undefined as number | undefined,
authType: 'WEB' as 'WEB' | 'OAUTH2' | 'SAML2',
forceChange: false, forceChange: false,
}); });
@ -120,11 +121,17 @@ export default function InviteMembersModal({ opened, onClose, onSuccess }: Invit
})); }));
const handleInviteUser = async () => { const handleInviteUser = async () => {
if (!inviteForm.username || !inviteForm.password) { if (!inviteForm.username) {
alert({ alertType: 'error', title: t('workspace.people.addMember.usernameRequired') }); alert({ alertType: 'error', title: t('workspace.people.addMember.usernameRequired') });
return; 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 { try {
setProcessing(true); setProcessing(true);
await userManagementService.createUser({ await userManagementService.createUser({
@ -132,7 +139,7 @@ export default function InviteMembersModal({ opened, onClose, onSuccess }: Invit
password: inviteForm.password, password: inviteForm.password,
role: inviteForm.role, role: inviteForm.role,
teamId: inviteForm.teamId, teamId: inviteForm.teamId,
authType: 'password', authType: inviteForm.authType === 'WEB' ? 'password' : 'SSO',
forceChange: inviteForm.forceChange, forceChange: inviteForm.forceChange,
}); });
alert({ alertType: 'success', title: t('workspace.people.addMember.success') }); alert({ alertType: 'success', title: t('workspace.people.addMember.success') });
@ -144,6 +151,7 @@ export default function InviteMembersModal({ opened, onClose, onSuccess }: Invit
password: '', password: '',
role: 'ROLE_USER', role: 'ROLE_USER',
teamId: undefined, teamId: undefined,
authType: 'WEB',
forceChange: false, forceChange: false,
}); });
} catch (error: any) { } catch (error: any) {
@ -243,6 +251,7 @@ export default function InviteMembersModal({ opened, onClose, onSuccess }: Invit
password: '', password: '',
role: 'ROLE_USER', role: 'ROLE_USER',
teamId: undefined, teamId: undefined,
authType: 'WEB',
forceChange: false, forceChange: false,
}); });
setEmailInviteForm({ setEmailInviteForm({
@ -501,14 +510,42 @@ export default function InviteMembersModal({ opened, onClose, onSuccess }: Invit
onChange={(e) => setInviteForm({ ...inviteForm, username: e.currentTarget.value })} onChange={(e) => setInviteForm({ ...inviteForm, username: e.currentTarget.value })}
required required
/> />
<TextInput
label={t('workspace.people.addMember.password')} {/* Auth Type Selector - only show if SSO is enabled */}
type="password" {(config?.enableOAuth || config?.enableSaml) && (
placeholder={t('workspace.people.addMember.passwordPlaceholder')} <Select
value={inviteForm.password} label={t('workspace.people.addMember.authType', 'Authentication Type')}
onChange={(e) => setInviteForm({ ...inviteForm, password: e.currentTarget.value })} data={[
required { value: 'WEB', label: t('workspace.people.authType.password', 'Password') },
/> ...(config?.enableOAuth ? [{ value: 'OAUTH2', label: t('workspace.people.authType.oauth', 'OAuth2') }] : []),
...(config?.enableSaml ? [{ value: 'SAML2', label: t('workspace.people.authType.saml', 'SAML2') }] : []),
]}
value={inviteForm.authType}
onChange={(value) => setInviteForm({ ...inviteForm, authType: (value as 'WEB' | 'OAUTH2' | 'SAML2') || 'WEB' })}
comboboxProps={{ withinPortal: true, zIndex: Z_INDEX_OVER_CONFIG_MODAL }}
description={inviteForm.authType !== 'WEB' ? t('workspace.people.authType.ssoDescription', 'User will authenticate via SSO provider') : undefined}
/>
)}
{/* Password field - only required for WEB auth type */}
{inviteForm.authType === 'WEB' && (
<>
<TextInput
label={t('workspace.people.addMember.password')}
type="password"
placeholder={t('workspace.people.addMember.passwordPlaceholder')}
value={inviteForm.password}
onChange={(e) => setInviteForm({ ...inviteForm, password: e.currentTarget.value })}
required
/>
<Checkbox
label={t('workspace.people.addMember.forcePasswordChange', 'Force password change on first login')}
checked={inviteForm.forceChange}
onChange={(e) => setInviteForm({ ...inviteForm, forceChange: e.currentTarget.checked })}
/>
</>
)}
<Select <Select
label={t('workspace.people.addMember.role')} label={t('workspace.people.addMember.role')}
data={roleOptions} data={roleOptions}
@ -525,11 +562,6 @@ export default function InviteMembersModal({ opened, onClose, onSuccess }: Invit
clearable clearable
comboboxProps={{ withinPortal: true, zIndex: Z_INDEX_OVER_CONFIG_MODAL }} comboboxProps={{ withinPortal: true, zIndex: Z_INDEX_OVER_CONFIG_MODAL }}
/> />
<Checkbox
label={t('workspace.people.addMember.forcePasswordChange', 'Force password change on first login')}
checked={inviteForm.forceChange}
onChange={(e) => setInviteForm({ ...inviteForm, forceChange: e.currentTarget.checked })}
/>
</> </>
)} )}