diff --git a/app/common/src/main/java/stirling/software/common/model/ApplicationProperties.java b/app/common/src/main/java/stirling/software/common/model/ApplicationProperties.java index 7da9d76c6c..9ed2610f51 100644 --- a/app/common/src/main/java/stirling/software/common/model/ApplicationProperties.java +++ b/app/common/src/main/java/stirling/software/common/model/ApplicationProperties.java @@ -595,7 +595,7 @@ public class ApplicationProperties { public static class SAML2 { private String provider; private Boolean enabled = false; - private Boolean autoCreateUser = false; + private Boolean autoCreateUser = true; private Boolean blockRegistration = false; private String registrationId = "stirling"; @@ -672,7 +672,7 @@ public class ApplicationProperties { private String issuer; private String clientId; @ToString.Exclude private String clientSecret; - private Boolean autoCreateUser = false; + private Boolean autoCreateUser = true; private Boolean blockRegistration = false; private String useAsUsername; private Collection scopes = new ArrayList<>(); @@ -743,7 +743,6 @@ public class ApplicationProperties { @Data public static class Jwt { private boolean enableKeystore = true; - private boolean enableKeyRotation = false; private boolean enableKeyCleanup = true; /** @@ -847,8 +846,8 @@ public class ApplicationProperties { @Data public static class Trust { private boolean serverAsAnchor = true; - private boolean useSystemTrust = false; - private boolean useMozillaBundle = false; + private boolean useSystemTrust = true; + private boolean useMozillaBundle = true; private boolean useAATL = false; private boolean useEUTL = false; } @@ -891,10 +890,10 @@ public class ApplicationProperties { private Boolean enableAnalytics; private Boolean enablePosthog; private Boolean enableScarf; - private Boolean enableDesktopInstallSlide; + private Boolean enableDesktopInstallSlide = true; private Datasource datasource; private boolean disableSanitize; - private int maxDPI; + private int maxDPI = 500; private boolean enableUrlToPDF; private Html html = new Html(); private CustomPaths customPaths = new CustomPaths(); @@ -908,8 +907,9 @@ public class ApplicationProperties { private String frontendUrl; // Frontend URL for invite email links (e.g. // 'https://app.example.com'). If not set, falls back to backendUrl. - private boolean enableMobileScanner = false; // Enable mobile phone QR code upload feature + private boolean enableMobileScanner = true; // Enable mobile phone QR code upload feature private MobileScannerSettings mobileScannerSettings = new MobileScannerSettings(); + private ServerCertificate serverCertificate = new ServerCertificate(); @Data public static class MobileScannerSettings { @@ -919,6 +919,16 @@ public class ApplicationProperties { private boolean stretchToFit = false; // Whether to stretch image to fill page } + @Data + public static class ServerCertificate { + private boolean enabled = + true; // Enable server-side "Sign with Stirling-PDF" certificate + private String organizationName = "Stirling PDF Inc"; + private int validity = 365; // Certificate validity in days + private boolean regenerateOnStartup = + false; // Generate a new certificate on each startup + } + public boolean isAnalyticsEnabled() { return this.enableAnalytics != null && this.enableAnalytics; } @@ -1003,7 +1013,7 @@ public class ApplicationProperties { @Data public static class Sharing { private boolean enabled = false; - private boolean linkEnabled = false; + private boolean linkEnabled = true; private boolean emailEnabled = false; private int linkExpirationDays = 3; } @@ -1177,7 +1187,7 @@ public class ApplicationProperties { @Data public static class Metrics { - private boolean enabled; + private boolean enabled = true; } @Data @@ -1229,7 +1239,7 @@ public class ApplicationProperties { private boolean enableInvites = false; private int inviteLinkExpiryHours = 72; // Default: 72 hours (3 days) private String host; - private int port; + private int port = 587; private String username; @ToString.Exclude private String password; private String from; @@ -1256,10 +1266,10 @@ public class ApplicationProperties { @ToString.Exclude private String botToken; private String botUsername; private String pipelineInboxFolder = "telegram"; - private Boolean customFolderSuffix = false; - private Boolean enableAllowUserIDs = false; + private Boolean customFolderSuffix = true; + private Boolean enableAllowUserIDs = true; private List allowUserIDs = new ArrayList<>(); - private Boolean enableAllowChannelIDs = false; + private Boolean enableAllowChannelIDs = true; private List allowChannelIDs = new ArrayList<>(); private long processingTimeoutSeconds = 180; private long pollingIntervalMillis = 2000; diff --git a/app/core/src/main/java/stirling/software/SPDF/config/WebMvcConfig.java b/app/core/src/main/java/stirling/software/SPDF/config/WebMvcConfig.java index dac9816018..2726f8d764 100644 --- a/app/core/src/main/java/stirling/software/SPDF/config/WebMvcConfig.java +++ b/app/core/src/main/java/stirling/software/SPDF/config/WebMvcConfig.java @@ -85,7 +85,6 @@ public class WebMvcConfig implements WebMvcConfigurer { "/icons/**", "/modern-logo/**", "/classic-logo/**", - "/robots.txt", "/3rdPartyLicenses.json", "/pdfjs/**", "/pdfjs-legacy/**", diff --git a/app/core/src/main/java/stirling/software/SPDF/controller/web/RobotsController.java b/app/core/src/main/java/stirling/software/SPDF/controller/web/RobotsController.java new file mode 100644 index 0000000000..cf4c9ba610 --- /dev/null +++ b/app/core/src/main/java/stirling/software/SPDF/controller/web/RobotsController.java @@ -0,0 +1,30 @@ +package stirling.software.SPDF.controller.web; + +import org.springframework.http.MediaType; +import org.springframework.web.bind.annotation.GetMapping; +import org.springframework.web.bind.annotation.ResponseBody; +import org.springframework.web.bind.annotation.RestController; + +import stirling.software.common.model.ApplicationProperties; + +/** + * Serves /robots.txt dynamically so the system.googlevisibility flag actually controls + * search-engine indexing. 'true' returns an allow-all policy; 'false' returns a disallow-all policy + * to keep the instance out of search engines (useful for embedded/internal deployments). + */ +@RestController +public class RobotsController { + + private final ApplicationProperties applicationProperties; + + public RobotsController(ApplicationProperties applicationProperties) { + this.applicationProperties = applicationProperties; + } + + @GetMapping(value = "/robots.txt", produces = MediaType.TEXT_PLAIN_VALUE) + @ResponseBody + public String robotsTxt() { + boolean allowIndexing = applicationProperties.getSystem().isGooglevisibility(); + return "User-agent: *\n" + (allowIndexing ? "Allow: /\n" : "Disallow: /\n"); + } +} diff --git a/app/core/src/main/resources/settings.yml.template b/app/core/src/main/resources/settings.yml.template index fe2b8635aa..85d97158b3 100644 --- a/app/core/src/main/resources/settings.yml.template +++ b/app/core/src/main/resources/settings.yml.template @@ -62,8 +62,6 @@ security: # IMPORTANT: For SAML setup, download your SP metadata from the BACKEND URL: http://localhost:8080/saml2/service-provider-metadata/{registrationId} # Do NOT use the frontend dev server URL (localhost:5173) as it will generate incorrect ACS URLs. Always use the backend URL (localhost:8080) for SAML configuration. jwt: # This feature is currently under development and not yet fully supported. Do not use in production. - persistence: true # Set to 'true' to enable JWT key store - enableKeyRotation: true # Set to 'true' to enable key pair rotation enableKeyCleanup: true # Set to 'true' to enable key pair cleanup tokenExpiryMinutes: 1440 # JWT access token lifetime in minutes for web clients (1 day). desktopTokenExpiryMinutes: 43200 # JWT access token lifetime in minutes for desktop clients (30 days). @@ -141,10 +139,10 @@ telegram: botUsername: "" # Telegram bot username (without @) pipelineInboxFolder: telegram # Name of the pipeline inbox folder for Telegram uploads customFolderSuffix: true # set to 'true' to allow users to specify custom target folders via UserID - enableAllowUserIDs: true # set to 'true' to restrict access to specific Telegram user IDs - allowUserIDs: [] # List of allowed Telegram user IDs (e.g. [123456789, 987654321]). Leave empty to allow all users. - enableAllowChannelIDs: true # set to 'true' to restrict access to specific Telegram channel IDs - allowChannelIDs: [] # List of allowed Telegram channel IDs (e.g. [-1001234567890, -1009876543210]). Leave empty to allow all channels. + enableAllowUserIDs: true # set to 'true' to restrict access to specific Telegram user IDs. NOTE: only takes effect when allowUserIDs is non-empty; with an empty list every user is still allowed even when this is 'true' + allowUserIDs: [] # List of allowed Telegram user IDs (e.g. [123456789, 987654321]). Leave empty to allow all users (the enableAllowUserIDs toggle has no effect until this list is populated). + enableAllowChannelIDs: true # set to 'true' to restrict access to specific Telegram channel IDs. NOTE: only takes effect when allowChannelIDs is non-empty; with an empty list every channel is still allowed even when this is 'true' + allowChannelIDs: [] # List of allowed Telegram channel IDs (e.g. [-1001234567890, -1009876543210]). Leave empty to allow all channels (the enableAllowChannelIDs toggle has no effect until this list is populated). processingTimeoutSeconds: 180 # Maximum time in seconds to wait for processing a Telegram request pollingIntervalMillis: 2000 # Interval in milliseconds between polling for new messages feedback: @@ -172,7 +170,7 @@ legal: system: defaultLocale: "" # force a default language for new users (e.g. 'en-US', 'de-DE'). Empty string auto-detects from the browser, falling back to en-US - googlevisibility: false # 'true' to allow Google visibility (via robots.txt), 'false' to disallow + googlevisibility: false # 'true' serves an allow-all /robots.txt; 'false' serves a disallow-all /robots.txt to keep the instance out of search engines enableAlphaFunctionality: false # set to enable functionality which might need more testing before it fully goes live (this feature might make no changes) showUpdate: true # see when a new update is available showUpdateOnlyAdmin: true # only admins can see when a new update is available, depending on showUpdate it must be set to 'true' @@ -186,7 +184,7 @@ system: enableUrlToPDF: false # Set to 'true' to enable URL to PDF, INTERNAL ONLY, known security issues, should not be used externally disableSanitize: false # set to true to disable Sanitize HTML; (can lead to injections in HTML) maxDPI: 500 # Maximum allowed DPI for PDF to image conversion - corsAllowedOrigins: [] # List of allowed origins for CORS (e.g. ['http://localhost:5173', 'https://app.example.com']). Leave empty to disable CORS. For local development with frontend on port 5173, add 'http://localhost:5173' + corsAllowedOrigins: [] # List of allowed origins for CORS (e.g. ['http://localhost:5173', 'https://app.example.com']). WARNING: leaving this empty falls back to allowing ALL origins (with credentials), it does NOT disable CORS. Set explicit origins to lock it down. backendUrl: "" # Backend base URL for SAML/OAuth/API callbacks (e.g. 'http://localhost:8080' for dev, 'https://api.example.com' for production). REQUIRED for SSO authentication to work correctly. This is where your IdP will send SAML responses and OAuth callbacks. Leave empty to default to 'http://localhost:8080' in development. frontendUrl: "" # Frontend URL for invite email links (e.g. 'https://app.example.com'). Optional - if not set, will use backendUrl. This is the URL users click in invite emails. enableMobileScanner: true # Enable mobile phone QR code upload feature. Requires frontendUrl to be configured. @@ -197,7 +195,7 @@ system: stretchToFit: false # Whether to stretch images to fill the entire page (may distort aspect ratio). If false, images are centered with preserved aspect ratio. Only applies when convertToPdf is true. serverCertificate: enabled: true # Enable server-side certificate for "Sign with Stirling-PDF" option - organizationName: Stirling-PDF # Organization name for generated certificates + organizationName: Stirling PDF Inc # Organization name for generated certificates validity: 365 # Certificate validity in days regenerateOnStartup: false # Generate new certificate on each startup html: @@ -304,7 +302,7 @@ autoPipeline: allowedExtensions: [] # Optional extension allow-list (case-insensitive, without the leading dot). Empty list = accept all extensions. Example: ["pdf", "tiff"] ui: - appNameNavbar: "" # name displayed on the navigation bar + appNameNavbar: "" # custom app/brand name. NOTE: no longer shown in the navbar (the navbar renders the logo). It IS used as the browser tab title and as the TOTP/2FA issuer label in authenticator apps. Empty falls back to "Stirling PDF" logoStyle: classic # Options: 'classic' (default - classic S icon) or 'modern' (minimalist logo) languages: [] # If empty, all languages are enabled. To restrict to specific languages, use a whitelist like ["de_DE", "pl_PL", "sv_SE"]. Empty list or not restricting any languages will enable all available languages. defaultHideUnavailableTools: false # Default user preference: hide disabled tools instead of greying them out diff --git a/app/core/src/test/java/stirling/software/SPDF/service/CertificateValidationServiceMoreTest.java b/app/core/src/test/java/stirling/software/SPDF/service/CertificateValidationServiceMoreTest.java index cbf0d30113..f133dafdcb 100644 --- a/app/core/src/test/java/stirling/software/SPDF/service/CertificateValidationServiceMoreTest.java +++ b/app/core/src/test/java/stirling/software/SPDF/service/CertificateValidationServiceMoreTest.java @@ -89,9 +89,13 @@ class CertificateValidationServiceMoreTest { } private static ApplicationProperties defaultProps() { - // Real POJO defaults: trust all off, revocation "none". + // Test baseline: every trust source explicitly off so each test enables only what it + // exercises (the shipped POJO defaults now enable system + Mozilla trust). ApplicationProperties props = new ApplicationProperties(); - props.getSecurity().getValidation().getTrust().setServerAsAnchor(false); + var trust = props.getSecurity().getValidation().getTrust(); + trust.setServerAsAnchor(false); + trust.setUseSystemTrust(false); + trust.setUseMozillaBundle(false); return props; } diff --git a/app/proprietary/src/main/java/stirling/software/proprietary/service/ServerCertificateService.java b/app/proprietary/src/main/java/stirling/software/proprietary/service/ServerCertificateService.java index 44d750e8e4..09b767a6d1 100644 --- a/app/proprietary/src/main/java/stirling/software/proprietary/service/ServerCertificateService.java +++ b/app/proprietary/src/main/java/stirling/software/proprietary/service/ServerCertificateService.java @@ -22,12 +22,12 @@ import org.bouncycastle.cert.jcajce.JcaX509v3CertificateBuilder; import org.bouncycastle.jce.provider.BouncyCastleProvider; import org.bouncycastle.operator.ContentSigner; import org.bouncycastle.operator.jcajce.JcaContentSignerBuilder; -import org.springframework.beans.factory.annotation.Value; import org.springframework.stereotype.Service; import lombok.extern.slf4j.Slf4j; import stirling.software.common.configuration.InstallationPathConfig; +import stirling.software.common.model.ApplicationProperties; import stirling.software.common.service.ServerCertificateServiceInterface; import stirling.software.proprietary.security.configuration.ee.KeygenLicenseVerifier.License; import stirling.software.proprietary.security.configuration.ee.LicenseKeyChecker; @@ -40,22 +40,22 @@ public class ServerCertificateService implements ServerCertificateServiceInterfa private static final String KEYSTORE_ALIAS = "stirling-pdf-server"; private static final String DEFAULT_PASSWORD = "stirling-pdf-server-cert"; - @Value("${system.serverCertificate.enabled:false}") private boolean enabled; - - @Value("${system.serverCertificate.organizationName:Stirling-PDF}") private String organizationName; - - @Value("${system.serverCertificate.validity:365}") private int validityDays; - - @Value("${system.serverCertificate.regenerateOnStartup:false}") private boolean regenerateOnStartup; private final LicenseKeyChecker licenseKeyChecker; - public ServerCertificateService(LicenseKeyChecker licenseKeyChecker) { + public ServerCertificateService( + LicenseKeyChecker licenseKeyChecker, ApplicationProperties applicationProperties) { this.licenseKeyChecker = licenseKeyChecker; + ApplicationProperties.System.ServerCertificate config = + applicationProperties.getSystem().getServerCertificate(); + this.enabled = config.isEnabled(); + this.organizationName = config.getOrganizationName(); + this.validityDays = config.getValidity(); + this.regenerateOnStartup = config.isRegenerateOnStartup(); } static { diff --git a/app/proprietary/src/main/java/stirling/software/proprietary/workflow/service/UserServerCertificateService.java b/app/proprietary/src/main/java/stirling/software/proprietary/workflow/service/UserServerCertificateService.java index 5f73deb322..a21b0a105c 100644 --- a/app/proprietary/src/main/java/stirling/software/proprietary/workflow/service/UserServerCertificateService.java +++ b/app/proprietary/src/main/java/stirling/software/proprietary/workflow/service/UserServerCertificateService.java @@ -81,7 +81,7 @@ public class UserServerCertificateService { // Certificate details with username String username = user.getUsername(); - X500Name subject = new X500Name("CN=" + username + ", O=Stirling-PDF User, C=US"); + X500Name subject = new X500Name("CN=" + username + ", OU=User, O=Stirling PDF Inc, C=US"); BigInteger serialNumber = BigInteger.valueOf(System.currentTimeMillis()); Date notBefore = new Date(); Date notAfter = diff --git a/app/proprietary/src/test/java/stirling/software/proprietary/service/ServerCertificateServiceTest.java b/app/proprietary/src/test/java/stirling/software/proprietary/service/ServerCertificateServiceTest.java index 4d78ca7471..e13bc7b0b4 100644 --- a/app/proprietary/src/test/java/stirling/software/proprietary/service/ServerCertificateServiceTest.java +++ b/app/proprietary/src/test/java/stirling/software/proprietary/service/ServerCertificateServiceTest.java @@ -25,6 +25,7 @@ import org.mockito.junit.jupiter.MockitoExtension; import org.springframework.test.util.ReflectionTestUtils; import stirling.software.common.configuration.InstallationPathConfig; +import stirling.software.common.model.ApplicationProperties; import stirling.software.proprietary.security.configuration.ee.KeygenLicenseVerifier.License; import stirling.software.proprietary.security.configuration.ee.LicenseKeyChecker; @@ -48,7 +49,7 @@ class ServerCertificateServiceTest { @BeforeEach void setUp() { - service = new ServerCertificateService(licenseKeyChecker); + service = new ServerCertificateService(licenseKeyChecker, new ApplicationProperties()); // default: feature enabled, validity 365, org Stirling-PDF, no regenerate ReflectionTestUtils.setField(service, "enabled", true); ReflectionTestUtils.setField(service, "organizationName", "Stirling-PDF"); diff --git a/frontend/editor/src/proprietary/components/shared/config/configSections/AdminFeaturesSection.tsx b/frontend/editor/src/proprietary/components/shared/config/configSections/AdminFeaturesSection.tsx index 86edd74446..961c6f6ce4 100644 --- a/frontend/editor/src/proprietary/components/shared/config/configSections/AdminFeaturesSection.tsx +++ b/frontend/editor/src/proprietary/components/shared/config/configSections/AdminFeaturesSection.tsx @@ -67,7 +67,7 @@ export default function AdminFeaturesSection() { } = { serverCertificate: systemData.serverCertificate || { enabled: true, - organizationName: "Stirling-PDF", + organizationName: "Stirling PDF Inc", validity: 365, regenerateOnStartup: false, }, @@ -258,7 +258,8 @@ export default function AdminFeaturesSection() { "Organization name for generated certificates", )} value={ - settings.serverCertificate?.organizationName || "Stirling-PDF" + settings.serverCertificate?.organizationName || + "Stirling PDF Inc" } onChange={(e) => setSettings({ diff --git a/frontend/editor/src/proprietary/components/shared/config/configSections/AdminSecuritySection.tsx b/frontend/editor/src/proprietary/components/shared/config/configSections/AdminSecuritySection.tsx index a993c3156e..448ebe3fc1 100644 --- a/frontend/editor/src/proprietary/components/shared/config/configSections/AdminSecuritySection.tsx +++ b/frontend/editor/src/proprietary/components/shared/config/configSections/AdminSecuritySection.tsx @@ -35,8 +35,6 @@ interface SecuritySettingsData { loginResetTimeMinutes?: number; xFrameOptions?: string; jwt?: { - persistence?: boolean; - enableKeyRotation?: boolean; enableKeyCleanup?: boolean; tokenExpiryMinutes?: number; desktopTokenExpiryMinutes?: number; @@ -163,9 +161,6 @@ export default function AdminSecuritySection() { securitySettings.loginResetTimeMinutes, "security.xFrameOptions": securitySettings.xFrameOptions, // JWT settings - "security.jwt.persistence": securitySettings.jwt?.persistence, - "security.jwt.enableKeyRotation": - securitySettings.jwt?.enableKeyRotation, "security.jwt.enableKeyCleanup": securitySettings.jwt?.enableKeyCleanup, "security.jwt.tokenExpiryMinutes": securitySettings.jwt?.tokenExpiryMinutes, @@ -519,83 +514,6 @@ export default function AdminSecuritySection() { {t("admin.settings.security.jwt.label", "JWT Configuration")} -
-
- - {t( - "admin.settings.security.jwt.persistence.label", - "Enable Key Persistence", - )} - - - {t( - "admin.settings.security.jwt.persistence.description", - "Store JWT keys persistently (required for multi-instance deployments)", - )} - -
- - - setSettings({ - ...settings, - jwt: { ...settings?.jwt, persistence: e.target.checked }, - }) - } - disabled={!loginEnabled} - /> - - -
- -
-
- - {t( - "admin.settings.security.jwt.enableKeyRotation.label", - "Enable Key Rotation", - )} - - - {t( - "admin.settings.security.jwt.enableKeyRotation.description", - "Automatically rotate JWT signing keys for improved security", - )} - -
- - - setSettings({ - ...settings, - jwt: { - ...settings?.jwt, - enableKeyRotation: e.target.checked, - }, - }) - } - disabled={!loginEnabled} - /> - - -
-