Bug fixing and debugs (#5704)

Co-authored-by: ConnorYoh <40631091+ConnorYoh@users.noreply.github.com>
This commit is contained in:
Anthony Stirling
2026-02-11 18:43:29 +00:00
committed by GitHub
parent 5df466266a
commit f9d2f36ab7
20 changed files with 385 additions and 54 deletions

View File

@@ -0,0 +1,29 @@
package stirling.software.common.service;
/**
* Interface for checking license status dynamically. Implementation provided by proprietary module
* when available.
*/
public interface LicenseServiceInterface {
/**
* Get the license type as a string.
*
* @return "NORMAL", "SERVER", or "ENTERPRISE"
*/
String getLicenseTypeName();
/**
* Check if running Pro or higher (SERVER or ENTERPRISE license).
*
* @return true if SERVER or ENTERPRISE license is active
*/
boolean isRunningProOrHigher();
/**
* Check if running Enterprise edition.
*
* @return true if ENTERPRISE license is active
*/
boolean isRunningEE();
}

View File

@@ -168,6 +168,7 @@ def generatedFrontendPaths = [
]
tasks.register('npmInstall', Exec) {
doNotTrackState("node_modules contains symlinks that Gradle cannot snapshot on Windows/WSL")
enabled = buildWithFrontend
group = 'frontend'
description = 'Install frontend dependencies'
@@ -214,6 +215,7 @@ tasks.register('npmInstall', Exec) {
}
tasks.register('npmBuild', Exec) {
doNotTrackState("Frontend build depends on untracked npmInstall task")
enabled = buildWithFrontend
group = 'frontend'
description = 'Build frontend application'

View File

@@ -35,6 +35,7 @@ public class ConfigController {
private final EndpointConfiguration endpointConfiguration;
private final ServerCertificateServiceInterface serverCertificateService;
private final UserServiceInterface userService;
private final stirling.software.common.service.LicenseServiceInterface licenseService;
private final stirling.software.SPDF.config.ExternalAppDepConfig externalAppDepConfig;
public ConfigController(
@@ -45,15 +46,66 @@ public class ConfigController {
ServerCertificateServiceInterface serverCertificateService,
@org.springframework.beans.factory.annotation.Autowired(required = false)
UserServiceInterface userService,
@org.springframework.beans.factory.annotation.Autowired(required = false)
stirling.software.common.service.LicenseServiceInterface licenseService,
stirling.software.SPDF.config.ExternalAppDepConfig externalAppDepConfig) {
this.applicationProperties = applicationProperties;
this.applicationContext = applicationContext;
this.endpointConfiguration = endpointConfiguration;
this.serverCertificateService = serverCertificateService;
this.userService = userService;
this.licenseService = licenseService;
this.externalAppDepConfig = externalAppDepConfig;
}
/**
* Get current license type dynamically instead of from cached bean. This ensures the frontend
* sees updated license status after admin changes the license key.
*/
private String getCurrentLicenseType() {
// Use LicenseService for fresh license status if available
if (licenseService != null) {
return licenseService.getLicenseTypeName();
}
// Fallback to cached bean if service not available
if (applicationContext.containsBean("license")) {
return applicationContext.getBean("license", String.class);
}
return null;
}
/** Check if running Pro or higher (SERVER or ENTERPRISE license) dynamically. */
private Boolean isRunningProOrHigher() {
// Use LicenseService for fresh license status if available
if (licenseService != null) {
return licenseService.isRunningProOrHigher();
}
// Fallback to cached bean
if (applicationContext.containsBean("runningProOrHigher")) {
return applicationContext.getBean("runningProOrHigher", Boolean.class);
}
return null;
}
/** Check if running Enterprise edition dynamically. */
private Boolean isRunningEE() {
// Use LicenseService for fresh license status if available
if (licenseService != null) {
return licenseService.isRunningEE();
}
// Fallback to cached bean
if (applicationContext.containsBean("runningEE")) {
return applicationContext.getBean("runningEE", Boolean.class);
}
return null;
}
@GetMapping("/app-config")
public ResponseEntity<Map<String, Object>> getAppConfig() {
Map<String, Object> configData = new HashMap<>();
@@ -185,19 +237,23 @@ public class ConfigController {
applicationProperties.getLegal().getAccessibilityStatement());
// Try to get EEAppConfig values if available
// Get these dynamically to reflect current license status (not cached at startup)
try {
if (applicationContext.containsBean("runningProOrHigher")) {
configData.put(
"runningProOrHigher",
applicationContext.getBean("runningProOrHigher", Boolean.class));
Boolean runningProOrHigher = isRunningProOrHigher();
if (runningProOrHigher != null) {
configData.put("runningProOrHigher", runningProOrHigher);
}
if (applicationContext.containsBean("runningEE")) {
configData.put(
"runningEE", applicationContext.getBean("runningEE", Boolean.class));
Boolean runningEE = isRunningEE();
if (runningEE != null) {
configData.put("runningEE", runningEE);
}
if (applicationContext.containsBean("license")) {
configData.put("license", applicationContext.getBean("license", String.class));
String licenseType = getCurrentLicenseType();
if (licenseType != null) {
configData.put("license", licenseType);
}
if (applicationContext.containsBean("SSOAutoLogin")) {
configData.put(
"SSOAutoLogin",

View File

@@ -0,0 +1,51 @@
package stirling.software.proprietary.security.configuration.ee;
import org.springframework.stereotype.Service;
import lombok.RequiredArgsConstructor;
import stirling.software.common.service.LicenseServiceInterface;
import stirling.software.proprietary.security.configuration.ee.KeygenLicenseVerifier.License;
/**
* Service that provides dynamic license checking instead of cached beans. This ensures that when
* admins update the license key, the changes are immediately reflected in the UI and config
* endpoints without requiring a restart.
*
* <p>Note: Some components (EnterpriseEndpointAspect, PremiumEndpointAspect, filters) still inject
* cached beans at startup for performance. These will require a restart to reflect license changes.
* This is acceptable because: 1. Most deployments add licenses during initial setup 2. License
* changes in production typically warrant a restart anyway 3. UI reflects changes immediately
* (banner disappears, license status updates)
*/
@Service
@RequiredArgsConstructor
public class DynamicLicenseService implements LicenseServiceInterface {
private final LicenseKeyChecker licenseKeyChecker;
/**
* Get the current license type dynamically (not cached).
*
* @return Current license: NORMAL, SERVER, or ENTERPRISE
*/
public License getCurrentLicense() {
return licenseKeyChecker.getPremiumLicenseEnabledResult();
}
@Override
public boolean isRunningProOrHigher() {
License license = getCurrentLicense();
return license == License.SERVER || license == License.ENTERPRISE;
}
@Override
public boolean isRunningEE() {
return getCurrentLicense() == License.ENTERPRISE;
}
@Override
public String getLicenseTypeName() {
return getCurrentLicense().name();
}
}

View File

@@ -69,27 +69,28 @@ public class JwtAuthenticationFilter extends OncePerRequestFilter {
if (!apiKeyExists(request, response)) {
String jwtToken = jwtService.extractToken(request);
if (jwtToken == null) {
// Allow auth endpoints to pass through without JWT
if (!isPublicAuthEndpoint(requestURI, contextPath)) {
// For API requests, return 401 JSON
String acceptHeader = request.getHeader("Accept");
if (requestURI.startsWith(contextPath + "/api/")
|| (acceptHeader != null
&& acceptHeader.contains("application/json"))) {
response.setStatus(HttpServletResponse.SC_UNAUTHORIZED);
response.setContentType("application/json");
response.getWriter().write("{\"error\":\"Authentication required\"}");
return;
}
// Check if this is a public endpoint BEFORE validating JWT
// This allows public endpoints to work even with expired tokens in the request
if (isPublicAuthEndpoint(requestURI, contextPath)) {
// For public auth endpoints, skip JWT validation and continue
filterChain.doFilter(request, response);
return;
}
// For HTML requests (SPA routes), let React Router handle it (serve
// index.html)
filterChain.doFilter(request, response);
if (jwtToken == null) {
// No JWT token and not a public endpoint
// For API requests, return 401 JSON
String acceptHeader = request.getHeader("Accept");
if (requestURI.startsWith(contextPath + "/api/")
|| (acceptHeader != null && acceptHeader.contains("application/json"))) {
response.setStatus(HttpServletResponse.SC_UNAUTHORIZED);
response.setContentType("application/json");
response.getWriter().write("{\"error\":\"Authentication required\"}");
return;
}
// For public auth endpoints without JWT, continue to the endpoint
// For HTML requests (SPA routes), let React Router handle it (serve
// index.html)
filterChain.doFilter(request, response);
return;
}