diff --git a/app/proprietary/src/main/java/stirling/software/proprietary/security/saml2/CustomSaml2AuthenticationSuccessHandler.java b/app/proprietary/src/main/java/stirling/software/proprietary/security/saml2/CustomSaml2AuthenticationSuccessHandler.java
index 0f350d7b4..b342fdcb4 100644
--- a/app/proprietary/src/main/java/stirling/software/proprietary/security/saml2/CustomSaml2AuthenticationSuccessHandler.java
+++ b/app/proprietary/src/main/java/stirling/software/proprietary/security/saml2/CustomSaml2AuthenticationSuccessHandler.java
@@ -67,19 +67,19 @@ public class CustomSaml2AuthenticationSuccessHandler
boolean userExists = userService.usernameExistsIgnoreCase(username);
- // Check if user is eligible for SAML (grandfathered or system has paid license)
+ // Check if user is eligible for SAML (grandfathered or system has ENTERPRISE license)
if (userExists) {
stirling.software.proprietary.security.model.User user =
userService.findByUsernameIgnoreCase(username).orElse(null);
- if (user != null && !licenseSettingsService.isOAuthEligible(user)) {
- // User is not grandfathered and no paid license - block SAML login
+ if (user != null && !licenseSettingsService.isSamlEligible(user)) {
+ // User is not grandfathered and no ENTERPRISE license - block SAML login
response.sendRedirect(
request.getContextPath() + "/logout?saml2RequiresLicense=true");
return;
}
- } else if (!licenseSettingsService.isOAuthEligible(null)) {
- // No existing user and no paid license -> block auto creation
+ } else if (!licenseSettingsService.isSamlEligible(null)) {
+ // No existing user and no ENTERPRISE license -> block auto creation
response.sendRedirect(
request.getContextPath() + "/logout?saml2RequiresLicense=true");
return;
diff --git a/app/proprietary/src/main/java/stirling/software/proprietary/service/UserLicenseSettingsService.java b/app/proprietary/src/main/java/stirling/software/proprietary/service/UserLicenseSettingsService.java
index 1cddefde3..d3bade89c 100644
--- a/app/proprietary/src/main/java/stirling/software/proprietary/service/UserLicenseSettingsService.java
+++ b/app/proprietary/src/main/java/stirling/software/proprietary/service/UserLicenseSettingsService.java
@@ -331,17 +331,17 @@ public class UserLicenseSettingsService {
}
/**
- * Checks if a user is eligible to use OAuth/SAML authentication.
+ * Checks if a user is eligible to use OAuth authentication.
*
*
A user is eligible if:
*
*
* - They are grandfathered for OAuth (existing user before policy change), OR
- *
- The system has an ENTERPRISE license (SSO is enterprise-only)
+ *
- The system has a paid license (SERVER or ENTERPRISE)
*
*
* @param user The user to check
- * @return true if the user can use OAuth/SAML
+ * @return true if the user can use OAuth
*/
public boolean isOAuthEligible(stirling.software.proprietary.security.model.User user) {
// Grandfathered users always have OAuth access
@@ -350,10 +350,36 @@ public class UserLicenseSettingsService {
return true;
}
- // Users can use OAuth/SAML only if system has ENTERPRISE license
- boolean hasEnterpriseLicense = hasEnterpriseLicense();
- log.debug("OAuth eligibility check: hasEnterpriseLicense={}", hasEnterpriseLicense);
- return hasEnterpriseLicense;
+ // Users can use OAuth with SERVER or ENTERPRISE license
+ boolean hasPaid = hasPaidLicense();
+ log.debug("OAuth eligibility check: hasPaidLicense={}", hasPaid);
+ return hasPaid;
+ }
+
+ /**
+ * Checks if a user is eligible to use SAML authentication.
+ *
+ * A user is eligible if:
+ *
+ *
+ * - They are grandfathered for OAuth (existing user before policy change), OR
+ *
- The system has an ENTERPRISE license (SAML is enterprise-only)
+ *
+ *
+ * @param user The user to check
+ * @return true if the user can use SAML
+ */
+ public boolean isSamlEligible(stirling.software.proprietary.security.model.User user) {
+ // Grandfathered users always have SAML access
+ if (user != null && user.isOauthGrandfathered()) {
+ log.debug("User {} is grandfathered for SAML", user.getUsername());
+ return true;
+ }
+
+ // Users can use SAML only with ENTERPRISE license
+ boolean hasEnterprise = hasEnterpriseLicense();
+ log.debug("SAML eligibility check: hasEnterpriseLicense={}", hasEnterprise);
+ return hasEnterprise;
}
/**
@@ -500,8 +526,7 @@ public class UserLicenseSettingsService {
}
/**
- * Checks if the system has an ENTERPRISE license. Used for enterprise-only features like SSO
- * (OAuth/SAML).
+ * Checks if the system has an ENTERPRISE license. Used for enterprise-only features like SAML.
*
* @return true if ENTERPRISE license is active
*/
diff --git a/frontend/public/locales/en-GB/translation.toml b/frontend/public/locales/en-GB/translation.toml
index 2d2fa19c6..b68a380d6 100644
--- a/frontend/public/locales/en-GB/translation.toml
+++ b/frontend/public/locales/en-GB/translation.toml
@@ -3539,8 +3539,8 @@ signinTitle = "Please sign in"
ssoSignIn = "Login via Single Sign-on"
oAuth2AutoCreateDisabled = "OAUTH2 Auto-Create User Disabled"
oAuth2AdminBlockedUser = "Registration or logging in of non-registered users is currently blocked. Please contact the administrator."
-oAuth2RequiresLicense = "OAuth/SSO login requires a paid license (Server or Enterprise). Please contact the administrator to upgrade your plan."
-saml2RequiresLicense = "SAML login requires a paid license (Server or Enterprise). Please contact the administrator to upgrade your plan."
+oAuth2RequiresLicense = "OAuth/SSO login requires a Server or Enterprise license. Please contact the administrator to upgrade your plan."
+saml2RequiresLicense = "SAML login requires an Enterprise license. Please contact the administrator to upgrade your plan."
maxUsersReached = "Maximum number of users reached for your current license. Please contact the administrator to upgrade your plan or add more seats."
oauth2RequestNotFound = "Authorization request not found"
oauth2InvalidUserInfoResponse = "Invalid User Info Response"
diff --git a/frontend/src/core/components/shared/config/configSections/providerDefinitions.ts b/frontend/src/core/components/shared/config/configSections/providerDefinitions.ts
index f51f997c7..f9d4af7c4 100644
--- a/frontend/src/core/components/shared/config/configSections/providerDefinitions.ts
+++ b/frontend/src/core/components/shared/config/configSections/providerDefinitions.ts
@@ -97,6 +97,7 @@ export const OAUTH2_PROVIDERS: Provider[] = [
icon: 'key-rounded',
type: 'oauth2',
scope: 'SSO',
+ businessTier: false, // Server tier - OAuth2/OIDC SSO
fields: [
{
key: 'issuer',
@@ -141,6 +142,7 @@ export const GENERIC_OAUTH2_PROVIDER: Provider = {
icon: 'link-rounded',
type: 'oauth2',
scope: 'SSO',
+ businessTier: false, // Server tier - OAuth2/OIDC SSO
fields: [
{
key: 'enabled',
@@ -262,8 +264,8 @@ export const SAML2_PROVIDER: Provider = {
name: 'SAML2',
icon: 'verified-user-rounded',
type: 'saml2',
- scope: 'SSO',
- businessTier: true,
+ scope: 'SSO (SAML)',
+ businessTier: true, // Enterprise tier - SAML only
fields: [
{
key: 'enabled',
diff --git a/frontend/src/proprietary/constants/planConstants.ts b/frontend/src/proprietary/constants/planConstants.ts
index ab14a2c55..f7073d25c 100644
--- a/frontend/src/proprietary/constants/planConstants.ts
+++ b/frontend/src/proprietary/constants/planConstants.ts
@@ -19,6 +19,7 @@ export const PLAN_FEATURES = {
{ name: 'Editing text in pdfs', included: false },
{ name: 'Users limited to seats', included: false },
{ name: 'SSO', included: false },
+ { name: 'SAML', included: false },
{ name: 'Auditing', included: false },
{ name: 'Usage tracking', included: false },
{ name: 'Prometheus Support', included: false },
@@ -37,7 +38,8 @@ export const PLAN_FEATURES = {
{ name: 'External Database', included: true },
{ name: 'Editing text in pdfs', included: true },
{ name: 'Users limited to seats', included: false },
- { name: 'SSO', included: false },
+ { name: 'SSO', included: true },
+ { name: 'SAML', included: false },
{ name: 'Auditing', included: false },
{ name: 'Usage tracking', included: false },
{ name: 'Prometheus Support', included: false },
@@ -57,6 +59,7 @@ export const PLAN_FEATURES = {
{ name: 'Editing text in pdfs', included: true },
{ name: 'Users limited to seats', included: true },
{ name: 'SSO', included: true },
+ { name: 'SAML', included: true },
{ name: 'Auditing', included: true },
{ name: 'Usage tracking', included: true },
{ name: 'Prometheus Support', included: true },
@@ -74,6 +77,7 @@ export const PLAN_HIGHLIGHTS = {
'Self-hosted on your infrastructure',
'Unlimited users',
'Advanced integrations',
+ 'SSO (OAuth2/OIDC)',
'Editing text in PDFs',
'Cancel anytime'
],
@@ -81,17 +85,18 @@ export const PLAN_HIGHLIGHTS = {
'Self-hosted on your infrastructure',
'Unlimited users',
'Advanced integrations',
+ 'SSO (OAuth2/OIDC)',
'Editing text in PDFs',
'Save with annual billing'
],
ENTERPRISE_MONTHLY: [
- 'Enterprise features (SSO, Auditing)',
+ 'Enterprise features (SAML, Auditing)',
'Usage tracking & Prometheus',
'Custom PDF metadata',
'Per-seat licensing'
],
ENTERPRISE_YEARLY: [
- 'Enterprise features (SSO, Auditing)',
+ 'Enterprise features (SAML, Auditing)',
'Usage tracking & Prometheus',
'Custom PDF metadata',
'Save with annual billing'