diff --git a/app/proprietary/src/main/java/stirling/software/proprietary/security/configuration/SecurityConfiguration.java b/app/proprietary/src/main/java/stirling/software/proprietary/security/configuration/SecurityConfiguration.java index 3f03dcbafd..06efcf3a1d 100644 --- a/app/proprietary/src/main/java/stirling/software/proprietary/security/configuration/SecurityConfiguration.java +++ b/app/proprietary/src/main/java/stirling/software/proprietary/security/configuration/SecurityConfiguration.java @@ -8,6 +8,7 @@ import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; import org.springframework.context.annotation.DependsOn; import org.springframework.context.annotation.Lazy; +import org.springframework.core.annotation.Order; import org.springframework.security.authentication.ProviderManager; import org.springframework.security.authentication.dao.DaoAuthenticationProvider; import org.springframework.security.config.annotation.method.configuration.EnableMethodSecurity; @@ -185,11 +186,39 @@ public class SecurityConfiguration { } @Bean + @Order(1) + public SecurityFilterChain samlFilterChain( + HttpSecurity http, + @Lazy IPRateLimitingFilter rateLimitingFilter, + @Lazy JwtAuthenticationFilter jwtAuthenticationFilter) + throws Exception { + http.securityMatcher("/saml2/**", "/login/saml2/**"); + + SessionCreationPolicy sessionPolicy = + (securityProperties.isSaml2Active() && runningProOrHigher) + ? SessionCreationPolicy.IF_REQUIRED + : SessionCreationPolicy.STATELESS; + + return configureSecurity(http, rateLimitingFilter, jwtAuthenticationFilter, sessionPolicy); + } + + @Bean + @Order(2) public SecurityFilterChain filterChain( HttpSecurity http, @Lazy IPRateLimitingFilter rateLimitingFilter, @Lazy JwtAuthenticationFilter jwtAuthenticationFilter) throws Exception { + SessionCreationPolicy sessionPolicy = SessionCreationPolicy.STATELESS; + return configureSecurity(http, rateLimitingFilter, jwtAuthenticationFilter, sessionPolicy); + } + + private SecurityFilterChain configureSecurity( + HttpSecurity http, + @Lazy IPRateLimitingFilter rateLimitingFilter, + @Lazy JwtAuthenticationFilter jwtAuthenticationFilter, + SessionCreationPolicy sessionPolicy) + throws Exception { // Enable CORS only if we have configured origins CorsConfigurationSource corsSource = corsConfigurationSource(); if (corsSource != null) { @@ -233,9 +262,7 @@ public class SecurityConfiguration { .addFilterBefore(jwtAuthenticationFilter, UserAuthenticationFilter.class); http.sessionManagement( - sessionManagement -> - sessionManagement.sessionCreationPolicy( - SessionCreationPolicy.STATELESS)); + sessionManagement -> sessionManagement.sessionCreationPolicy(sessionPolicy)); http.authenticationProvider(daoAuthenticationProvider()); http.requestCache(requestCache -> requestCache.requestCache(new NullRequestCache())); diff --git a/app/proprietary/src/main/java/stirling/software/proprietary/security/saml2/JwtSaml2AuthenticationRequestRepository.java b/app/proprietary/src/main/java/stirling/software/proprietary/security/saml2/JwtSaml2AuthenticationRequestRepository.java deleted file mode 100644 index d0508151c5..0000000000 --- a/app/proprietary/src/main/java/stirling/software/proprietary/security/saml2/JwtSaml2AuthenticationRequestRepository.java +++ /dev/null @@ -1,135 +0,0 @@ -package stirling.software.proprietary.security.saml2; - -import java.util.HashMap; -import java.util.Map; - -import org.springframework.security.saml2.provider.service.authentication.Saml2PostAuthenticationRequest; -import org.springframework.security.saml2.provider.service.registration.RelyingPartyRegistration; -import org.springframework.security.saml2.provider.service.registration.RelyingPartyRegistrationRepository; -import org.springframework.security.saml2.provider.service.web.Saml2AuthenticationRequestRepository; - -import jakarta.servlet.http.HttpServletRequest; -import jakarta.servlet.http.HttpServletResponse; - -import lombok.extern.slf4j.Slf4j; - -import stirling.software.proprietary.security.service.JwtServiceInterface; - -@Slf4j -public class JwtSaml2AuthenticationRequestRepository - implements Saml2AuthenticationRequestRepository { - private final Map tokenStore; - private final JwtServiceInterface jwtService; - private final RelyingPartyRegistrationRepository relyingPartyRegistrationRepository; - - private static final String SAML_REQUEST_TOKEN = "stirling_saml_request_token"; - - public JwtSaml2AuthenticationRequestRepository( - Map tokenStore, - JwtServiceInterface jwtService, - RelyingPartyRegistrationRepository relyingPartyRegistrationRepository) { - this.tokenStore = tokenStore; - this.jwtService = jwtService; - this.relyingPartyRegistrationRepository = relyingPartyRegistrationRepository; - } - - @Override - public void saveAuthenticationRequest( - Saml2PostAuthenticationRequest authRequest, - HttpServletRequest request, - HttpServletResponse response) { - if (!jwtService.isJwtEnabled()) { - log.debug("V2 is not enabled, skipping SAMLRequest token storage"); - return; - } - - if (authRequest == null) { - removeAuthenticationRequest(request, response); - return; - } - - Map claims = serializeSamlRequest(authRequest); - String token = jwtService.generateToken("", claims); - String relayState = authRequest.getRelayState(); - - tokenStore.put(relayState, token); - request.setAttribute(SAML_REQUEST_TOKEN, relayState); - response.addHeader(SAML_REQUEST_TOKEN, relayState); - - log.debug("Saved SAMLRequest token with RelayState: {}", relayState); - } - - @Override - public Saml2PostAuthenticationRequest loadAuthenticationRequest(HttpServletRequest request) { - String token = extractTokenFromStore(request); - - if (token == null) { - log.debug("No SAMLResponse token found in RelayState"); - return null; - } - - Map claims = jwtService.extractClaims(token); - return deserializeSamlRequest(claims); - } - - @Override - public Saml2PostAuthenticationRequest removeAuthenticationRequest( - HttpServletRequest request, HttpServletResponse response) { - Saml2PostAuthenticationRequest authRequest = loadAuthenticationRequest(request); - - String relayStateId = request.getParameter("RelayState"); - if (relayStateId != null) { - tokenStore.remove(relayStateId); - log.debug("Removed SAMLRequest token for RelayState ID: {}", relayStateId); - } - - return authRequest; - } - - private String extractTokenFromStore(HttpServletRequest request) { - String authnRequestId = request.getParameter("RelayState"); - - if (authnRequestId != null && !authnRequestId.isEmpty()) { - String token = tokenStore.get(authnRequestId); - - if (token != null) { - tokenStore.remove(authnRequestId); - log.debug("Retrieved SAMLRequest token for RelayState ID: {}", authnRequestId); - return token; - } else { - log.warn("No SAMLRequest token found for RelayState ID: {}", authnRequestId); - } - } - - return null; - } - - private Map serializeSamlRequest(Saml2PostAuthenticationRequest authRequest) { - Map claims = new HashMap<>(); - - claims.put("id", authRequest.getId()); - claims.put("relyingPartyRegistrationId", authRequest.getRelyingPartyRegistrationId()); - claims.put("authenticationRequestUri", authRequest.getAuthenticationRequestUri()); - claims.put("samlRequest", authRequest.getSamlRequest()); - claims.put("relayState", authRequest.getRelayState()); - - return claims; - } - - private Saml2PostAuthenticationRequest deserializeSamlRequest(Map claims) { - String relyingPartyRegistrationId = (String) claims.get("relyingPartyRegistrationId"); - RelyingPartyRegistration relyingPartyRegistration = - relyingPartyRegistrationRepository.findByRegistrationId(relyingPartyRegistrationId); - - if (relyingPartyRegistration == null) { - return null; - } - - return Saml2PostAuthenticationRequest.withRelyingPartyRegistration(relyingPartyRegistration) - .id((String) claims.get("id")) - .authenticationRequestUri((String) claims.get("authenticationRequestUri")) - .samlRequest((String) claims.get("samlRequest")) - .relayState((String) claims.get("relayState")) - .build(); - } -} diff --git a/app/proprietary/src/main/java/stirling/software/proprietary/security/saml2/Saml2Configuration.java b/app/proprietary/src/main/java/stirling/software/proprietary/security/saml2/Saml2Configuration.java index 6ccffa1dac..9cbde954fe 100644 --- a/app/proprietary/src/main/java/stirling/software/proprietary/security/saml2/Saml2Configuration.java +++ b/app/proprietary/src/main/java/stirling/software/proprietary/security/saml2/Saml2Configuration.java @@ -3,7 +3,6 @@ package stirling.software.proprietary.security.saml2; import java.security.cert.X509Certificate; import java.util.Collections; import java.util.UUID; -import java.util.concurrent.ConcurrentHashMap; import org.opensaml.saml.saml2.core.AuthnRequest; import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty; @@ -12,12 +11,10 @@ import org.springframework.context.annotation.Configuration; import org.springframework.core.io.Resource; import org.springframework.security.saml2.core.Saml2X509Credential; import org.springframework.security.saml2.core.Saml2X509Credential.Saml2X509CredentialType; -import org.springframework.security.saml2.provider.service.authentication.Saml2PostAuthenticationRequest; import org.springframework.security.saml2.provider.service.registration.InMemoryRelyingPartyRegistrationRepository; import org.springframework.security.saml2.provider.service.registration.RelyingPartyRegistration; import org.springframework.security.saml2.provider.service.registration.RelyingPartyRegistrationRepository; import org.springframework.security.saml2.provider.service.registration.Saml2MessageBinding; -import org.springframework.security.saml2.provider.service.web.Saml2AuthenticationRequestRepository; import org.springframework.security.saml2.provider.service.web.authentication.OpenSaml4AuthenticationRequestResolver; import jakarta.servlet.http.HttpServletRequest; @@ -27,7 +24,6 @@ import lombok.extern.slf4j.Slf4j; import stirling.software.common.model.ApplicationProperties; import stirling.software.common.model.ApplicationProperties.Security.SAML2; -import stirling.software.proprietary.security.service.JwtServiceInterface; @Configuration @Slf4j @@ -153,22 +149,10 @@ public class Saml2Configuration { return new InMemoryRelyingPartyRegistrationRepository(rp); } - @Bean - @ConditionalOnProperty(name = "security.saml2.enabled", havingValue = "true") - public Saml2AuthenticationRequestRepository - saml2AuthenticationRequestRepository( - JwtServiceInterface jwtService, - RelyingPartyRegistrationRepository relyingPartyRegistrationRepository) { - return new JwtSaml2AuthenticationRequestRepository( - new ConcurrentHashMap<>(), jwtService, relyingPartyRegistrationRepository); - } - @Bean @ConditionalOnProperty(name = "security.saml2.enabled", havingValue = "true") public OpenSaml4AuthenticationRequestResolver authenticationRequestResolver( - RelyingPartyRegistrationRepository relyingPartyRegistrationRepository, - Saml2AuthenticationRequestRepository - saml2AuthenticationRequestRepository) { + RelyingPartyRegistrationRepository relyingPartyRegistrationRepository) { OpenSaml4AuthenticationRequestResolver resolver = new OpenSaml4AuthenticationRequestResolver(relyingPartyRegistrationRepository); @@ -176,30 +160,10 @@ public class Saml2Configuration { customizer -> { HttpServletRequest request = customizer.getRequest(); AuthnRequest authnRequest = customizer.getAuthnRequest(); - Saml2PostAuthenticationRequest saml2AuthenticationRequest = - saml2AuthenticationRequestRepository.loadAuthenticationRequest(request); - if (saml2AuthenticationRequest != null) { - String sessionId = request.getSession(false).getId(); + // Generate a unique AuthnRequest ID for each SAML request + authnRequest.setID("ARQ" + UUID.randomUUID().toString().substring(1)); - log.debug( - "Retrieving SAML 2 authentication request ID from the current HTTP session {}", - sessionId); - - String authenticationRequestId = saml2AuthenticationRequest.getId(); - - if (!authenticationRequestId.isBlank()) { - authnRequest.setID(authenticationRequestId); - } else { - log.warn( - "No authentication request found for HTTP session {}. Generating new ID", - sessionId); - authnRequest.setID("ARQ" + UUID.randomUUID().toString().substring(1)); - } - } else { - log.debug("Generating new authentication request ID"); - authnRequest.setID("ARQ" + UUID.randomUUID().toString().substring(1)); - } logAuthnRequestDetails(authnRequest); logHttpRequestDetails(request); }); diff --git a/app/proprietary/src/main/java/stirling/software/proprietary/security/service/UserService.java b/app/proprietary/src/main/java/stirling/software/proprietary/security/service/UserService.java index f2bc3cc2cc..dbff599974 100644 --- a/app/proprietary/src/main/java/stirling/software/proprietary/security/service/UserService.java +++ b/app/proprietary/src/main/java/stirling/software/proprietary/security/service/UserService.java @@ -67,6 +67,7 @@ public class UserService implements UserServiceInterface { private final ApplicationProperties.Security.OAUTH2 oAuth2; + @Transactional public void processSSOPostLogin( String username, String ssoProviderId, diff --git a/app/proprietary/src/test/java/stirling/software/proprietary/security/saml2/JwtSaml2AuthenticationRequestRepositoryTest.java b/app/proprietary/src/test/java/stirling/software/proprietary/security/saml2/JwtSaml2AuthenticationRequestRepositoryTest.java deleted file mode 100644 index 915c97444c..0000000000 --- a/app/proprietary/src/test/java/stirling/software/proprietary/security/saml2/JwtSaml2AuthenticationRequestRepositoryTest.java +++ /dev/null @@ -1,243 +0,0 @@ -package stirling.software.proprietary.security.saml2; - -import static org.junit.jupiter.api.Assertions.*; -import static org.mockito.ArgumentMatchers.anyMap; -import static org.mockito.ArgumentMatchers.eq; -import static org.mockito.Mockito.mock; -import static org.mockito.Mockito.verify; -import static org.mockito.Mockito.when; - -import java.util.Map; -import java.util.concurrent.ConcurrentHashMap; - -import org.junit.jupiter.api.BeforeEach; -import org.junit.jupiter.api.Test; -import org.junit.jupiter.api.extension.ExtendWith; -import org.junit.jupiter.params.ParameterizedTest; -import org.junit.jupiter.params.provider.NullAndEmptySource; -import org.mockito.Mock; -import org.mockito.junit.jupiter.MockitoExtension; -import org.springframework.mock.web.MockHttpServletRequest; -import org.springframework.mock.web.MockHttpServletResponse; -import org.springframework.security.saml2.provider.service.authentication.Saml2PostAuthenticationRequest; -import org.springframework.security.saml2.provider.service.registration.AssertingPartyMetadata; -import org.springframework.security.saml2.provider.service.registration.RelyingPartyRegistration; -import org.springframework.security.saml2.provider.service.registration.RelyingPartyRegistrationRepository; - -import jakarta.servlet.http.HttpServletRequest; -import jakarta.servlet.http.HttpServletResponse; - -import stirling.software.proprietary.security.service.JwtServiceInterface; - -@ExtendWith(MockitoExtension.class) -class JwtSaml2AuthenticationRequestRepositoryTest { - - private static final String SAML_REQUEST_TOKEN = "stirling_saml_request_token"; - - private Map tokenStore; - - @Mock private JwtServiceInterface jwtService; - - @Mock private RelyingPartyRegistrationRepository relyingPartyRegistrationRepository; - - private JwtSaml2AuthenticationRequestRepository jwtSaml2AuthenticationRequestRepository; - - @BeforeEach - void setUp() { - tokenStore = new ConcurrentHashMap<>(); - jwtSaml2AuthenticationRequestRepository = - new JwtSaml2AuthenticationRequestRepository( - tokenStore, jwtService, relyingPartyRegistrationRepository); - } - - @Test - void saveAuthenticationRequest() { - var authRequest = mock(Saml2PostAuthenticationRequest.class); - var request = mock(MockHttpServletRequest.class); - var response = mock(MockHttpServletResponse.class); - String token = "testToken"; - String id = "testId"; - String relayState = "testRelayState"; - String authnRequestUri = "example.com/authnRequest"; - String samlRequest = "testSamlRequest"; - String relyingPartyRegistrationId = "stirling-pdf"; - - when(jwtService.isJwtEnabled()).thenReturn(true); - when(authRequest.getRelayState()).thenReturn(relayState); - when(authRequest.getId()).thenReturn(id); - when(authRequest.getAuthenticationRequestUri()).thenReturn(authnRequestUri); - when(authRequest.getSamlRequest()).thenReturn(samlRequest); - when(authRequest.getRelyingPartyRegistrationId()).thenReturn(relyingPartyRegistrationId); - when(jwtService.generateToken(eq(""), anyMap())).thenReturn(token); - - jwtSaml2AuthenticationRequestRepository.saveAuthenticationRequest( - authRequest, request, response); - - verify(request).setAttribute(SAML_REQUEST_TOKEN, relayState); - verify(response).addHeader(SAML_REQUEST_TOKEN, relayState); - } - - @Test - void saveAuthenticationRequestWithNullRequest() { - var request = mock(MockHttpServletRequest.class); - var response = mock(MockHttpServletResponse.class); - - jwtSaml2AuthenticationRequestRepository.saveAuthenticationRequest(null, request, response); - - assertTrue(tokenStore.isEmpty()); - } - - @Test - void loadAuthenticationRequest() { - var request = mock(MockHttpServletRequest.class); - var relyingPartyRegistration = mock(RelyingPartyRegistration.class); - var assertingPartyMetadata = mock(AssertingPartyMetadata.class); - String relayState = "testRelayState"; - String token = "testToken"; - Map claims = - Map.of( - "id", "testId", - "relyingPartyRegistrationId", "stirling-pdf", - "authenticationRequestUri", "example.com/authnRequest", - "samlRequest", "testSamlRequest", - "relayState", relayState); - - when(request.getParameter("RelayState")).thenReturn(relayState); - when(jwtService.extractClaims(token)).thenReturn(claims); - when(relyingPartyRegistrationRepository.findByRegistrationId("stirling-pdf")) - .thenReturn(relyingPartyRegistration); - when(relyingPartyRegistration.getRegistrationId()).thenReturn("stirling-pdf"); - when(relyingPartyRegistration.getAssertingPartyMetadata()) - .thenReturn(assertingPartyMetadata); - when(assertingPartyMetadata.getSingleSignOnServiceLocation()) - .thenReturn("https://example.com/sso"); - tokenStore.put(relayState, token); - - var result = jwtSaml2AuthenticationRequestRepository.loadAuthenticationRequest(request); - - assertNotNull(result); - assertFalse(tokenStore.containsKey(relayState)); - } - - @ParameterizedTest - @NullAndEmptySource - void loadAuthenticationRequestWithInvalidRelayState(String relayState) { - var request = mock(MockHttpServletRequest.class); - when(request.getParameter("RelayState")).thenReturn(relayState); - - var result = jwtSaml2AuthenticationRequestRepository.loadAuthenticationRequest(request); - - assertNull(result); - } - - @Test - void loadAuthenticationRequestWithNonExistentToken() { - var request = mock(MockHttpServletRequest.class); - when(request.getParameter("RelayState")).thenReturn("nonExistentRelayState"); - - var result = jwtSaml2AuthenticationRequestRepository.loadAuthenticationRequest(request); - - assertNull(result); - } - - @Test - void loadAuthenticationRequestWithNullRelyingPartyRegistration() { - var request = mock(MockHttpServletRequest.class); - String relayState = "testRelayState"; - String token = "testToken"; - Map claims = - Map.of( - "id", "testId", - "relyingPartyRegistrationId", "stirling-pdf", - "authenticationRequestUri", "example.com/authnRequest", - "samlRequest", "testSamlRequest", - "relayState", relayState); - - when(request.getParameter("RelayState")).thenReturn(relayState); - when(jwtService.extractClaims(token)).thenReturn(claims); - when(relyingPartyRegistrationRepository.findByRegistrationId("stirling-pdf")) - .thenReturn(null); - tokenStore.put(relayState, token); - - var result = jwtSaml2AuthenticationRequestRepository.loadAuthenticationRequest(request); - - assertNull(result); - } - - @Test - void removeAuthenticationRequest() { - var request = mock(HttpServletRequest.class); - var response = mock(HttpServletResponse.class); - var relyingPartyRegistration = mock(RelyingPartyRegistration.class); - var assertingPartyMetadata = mock(AssertingPartyMetadata.class); - String relayState = "testRelayState"; - String token = "testToken"; - Map claims = - Map.of( - "id", "testId", - "relyingPartyRegistrationId", "stirling-pdf", - "authenticationRequestUri", "example.com/authnRequest", - "samlRequest", "testSamlRequest", - "relayState", relayState); - - when(request.getParameter("RelayState")).thenReturn(relayState); - when(jwtService.extractClaims(token)).thenReturn(claims); - when(relyingPartyRegistrationRepository.findByRegistrationId("stirling-pdf")) - .thenReturn(relyingPartyRegistration); - when(relyingPartyRegistration.getRegistrationId()).thenReturn("stirling-pdf"); - when(relyingPartyRegistration.getAssertingPartyMetadata()) - .thenReturn(assertingPartyMetadata); - when(assertingPartyMetadata.getSingleSignOnServiceLocation()) - .thenReturn("https://example.com/sso"); - tokenStore.put(relayState, token); - - var result = - jwtSaml2AuthenticationRequestRepository.removeAuthenticationRequest( - request, response); - - assertNotNull(result); - assertFalse(tokenStore.containsKey(relayState)); - } - - @Test - void removeAuthenticationRequestWithNullRelayState() { - var request = mock(HttpServletRequest.class); - var response = mock(HttpServletResponse.class); - when(request.getParameter("RelayState")).thenReturn(null); - - var result = - jwtSaml2AuthenticationRequestRepository.removeAuthenticationRequest( - request, response); - - assertNull(result); - } - - @Test - void removeAuthenticationRequestWithNonExistentToken() { - var request = mock(HttpServletRequest.class); - var response = mock(HttpServletResponse.class); - when(request.getParameter("RelayState")).thenReturn("nonExistentRelayState"); - - var result = - jwtSaml2AuthenticationRequestRepository.removeAuthenticationRequest( - request, response); - - assertNull(result); - } - - @Test - void removeAuthenticationRequestWithOnlyRelayState() { - var request = mock(HttpServletRequest.class); - var response = mock(HttpServletResponse.class); - String relayState = "testRelayState"; - - when(request.getParameter("RelayState")).thenReturn(relayState); - - var result = - jwtSaml2AuthenticationRequestRepository.removeAuthenticationRequest( - request, response); - - assertNull(result); - assertFalse(tokenStore.containsKey(relayState)); - } -} diff --git a/build.gradle b/build.gradle index 950705d4f8..d4f26e4e91 100644 --- a/build.gradle +++ b/build.gradle @@ -67,7 +67,7 @@ springBoot { allprojects { group = 'stirling.software' - version = '2.4.1' + version = '2.4.2' configurations.configureEach { exclude group: 'commons-logging', module: 'commons-logging' diff --git a/frontend/src-tauri/tauri.conf.json b/frontend/src-tauri/tauri.conf.json index 41c446709c..76a6bc51ba 100644 --- a/frontend/src-tauri/tauri.conf.json +++ b/frontend/src-tauri/tauri.conf.json @@ -1,7 +1,7 @@ { "$schema": "../node_modules/@tauri-apps/cli/config.schema.json", "productName": "Stirling-PDF", - "version": "2.4.1", + "version": "2.4.2", "identifier": "stirling.pdf.dev", "build": { "frontendDist": "../dist", diff --git a/frontend/src/core/testing/serverExperienceSimulations.ts b/frontend/src/core/testing/serverExperienceSimulations.ts index a0948ba28d..86a6b41095 100644 --- a/frontend/src/core/testing/serverExperienceSimulations.ts +++ b/frontend/src/core/testing/serverExperienceSimulations.ts @@ -38,7 +38,7 @@ const FREE_LICENSE_INFO: LicenseInfo = { const BASE_NO_LOGIN_CONFIG: AppConfig = { enableAnalytics: true, - appVersion: '2.4.1', + appVersion: '2.4.2', serverCertificateEnabled: false, enableAlphaFunctionality: false, serverPort: 8080, diff --git a/frontend/src/desktop/components/AppProviders.tsx b/frontend/src/desktop/components/AppProviders.tsx index 8fcd9c64d2..02617cd0dc 100644 --- a/frontend/src/desktop/components/AppProviders.tsx +++ b/frontend/src/desktop/components/AppProviders.tsx @@ -6,7 +6,7 @@ import { SetupWizard } from '@app/components/SetupWizard'; import { useFirstLaunchCheck } from '@app/hooks/useFirstLaunchCheck'; import { useBackendInitializer } from '@app/hooks/useBackendInitializer'; import { DESKTOP_DEFAULT_APP_CONFIG } from '@app/config/defaultAppConfig'; -import { connectionModeService } from '@desktop/services/connectionModeService'; +import { connectionModeService } from '@app/services/connectionModeService'; import { tauriBackendService } from '@app/services/tauriBackendService'; import { authService } from '@app/services/authService'; diff --git a/frontend/src/desktop/components/SetupWizard/DesktopOAuthButtons.tsx b/frontend/src/desktop/components/SetupWizard/DesktopOAuthButtons.tsx index d0465f3c57..a7c5d5c343 100644 --- a/frontend/src/desktop/components/SetupWizard/DesktopOAuthButtons.tsx +++ b/frontend/src/desktop/components/SetupWizard/DesktopOAuthButtons.tsx @@ -3,7 +3,7 @@ import { useTranslation } from 'react-i18next'; import { authService, UserInfo } from '@app/services/authService'; import { buildOAuthCallbackHtml } from '@app/utils/oauthCallbackHtml'; import { BASE_PATH } from '@app/constants/app'; -import { STIRLING_SAAS_URL } from '@desktop/constants/connection'; +import { STIRLING_SAAS_URL } from '@app/constants/connection'; import '@app/routes/authShared/auth.css'; type KnownProviderId = 'google' | 'github' | 'keycloak' | 'azure' | 'apple' | 'oidc'; diff --git a/frontend/src/desktop/components/SetupWizard/index.tsx b/frontend/src/desktop/components/SetupWizard/index.tsx index 171684237b..a8e1f17ea9 100644 --- a/frontend/src/desktop/components/SetupWizard/index.tsx +++ b/frontend/src/desktop/components/SetupWizard/index.tsx @@ -8,7 +8,7 @@ import { SelfHostedLoginScreen } from '@app/components/SetupWizard/SelfHostedLog import { ServerConfig, connectionModeService } from '@app/services/connectionModeService'; import { AuthServiceError, authService, UserInfo } from '@app/services/authService'; import { tauriBackendService } from '@app/services/tauriBackendService'; -import { STIRLING_SAAS_URL } from '@desktop/constants/connection'; +import { STIRLING_SAAS_URL } from '@app/constants/connection'; import { listen } from '@tauri-apps/api/event'; import { useEffect } from 'react'; import '@app/routes/authShared/auth.css'; diff --git a/frontend/src/desktop/components/onboarding/orchestrator/useOnboardingOrchestrator.ts b/frontend/src/desktop/components/onboarding/orchestrator/useOnboardingOrchestrator.ts index a0e602ec95..fed28ed9df 100644 --- a/frontend/src/desktop/components/onboarding/orchestrator/useOnboardingOrchestrator.ts +++ b/frontend/src/desktop/components/onboarding/orchestrator/useOnboardingOrchestrator.ts @@ -1,6 +1,6 @@ /** * Desktop Override: Onboarding Orchestrator Hook - * + * * Simply wraps the core hook with the desktop-specific default runtime state * which has isDesktopApp set to true. */ @@ -9,7 +9,7 @@ import { useOnboardingOrchestrator as useCoreOnboardingOrchestrator, type UseOnboardingOrchestratorResult, } from '@core/components/onboarding/orchestrator/useOnboardingOrchestrator'; -import { DEFAULT_RUNTIME_STATE } from '@desktop/components/onboarding/orchestrator/onboardingConfig'; +import { DEFAULT_RUNTIME_STATE } from '@app/components/onboarding/orchestrator/onboardingConfig'; export type { OnboardingOrchestratorState, diff --git a/frontend/src/desktop/hooks/useEndpointConfig.ts b/frontend/src/desktop/hooks/useEndpointConfig.ts index 0b12868a1c..b59f2492e1 100644 --- a/frontend/src/desktop/hooks/useEndpointConfig.ts +++ b/frontend/src/desktop/hooks/useEndpointConfig.ts @@ -5,7 +5,7 @@ import apiClient from '@app/services/apiClient'; import { tauriBackendService } from '@app/services/tauriBackendService'; import { isBackendNotReadyError } from '@app/constants/backendErrors'; import type { EndpointAvailabilityDetails } from '@app/types/endpointAvailability'; -import { connectionModeService } from '@desktop/services/connectionModeService'; +import { connectionModeService } from '@app/services/connectionModeService'; import type { AppConfig } from '@app/contexts/AppConfigContext'; diff --git a/frontend/src/proprietary/testing/serverExperienceSimulations.ts b/frontend/src/proprietary/testing/serverExperienceSimulations.ts index 056a4aa09e..5d2d71fc6e 100644 --- a/frontend/src/proprietary/testing/serverExperienceSimulations.ts +++ b/frontend/src/proprietary/testing/serverExperienceSimulations.ts @@ -48,7 +48,7 @@ const FREE_LICENSE_INFO: LicenseInfo = { const BASE_NO_LOGIN_CONFIG: AppConfig = { enableAnalytics: true, - appVersion: '2.4.1', + appVersion: '2.4.2', serverCertificateEnabled: false, enableAlphaFunctionality: false, enableDesktopInstallSlide: true, diff --git a/frontend/tsconfig.core.json b/frontend/tsconfig.core.json index a06e67c0f9..bccf7f84b5 100644 --- a/frontend/tsconfig.core.json +++ b/frontend/tsconfig.core.json @@ -7,9 +7,10 @@ ] } }, - "exclude": [ - "src/proprietary", - "src/desktop", - "node_modules" + "include": [ + "src/*.js", + "src/*.ts", + "src/*.tsx", + "src/core" ] } diff --git a/frontend/tsconfig.core.vite.json b/frontend/tsconfig.core.vite.json new file mode 100644 index 0000000000..a06e67c0f9 --- /dev/null +++ b/frontend/tsconfig.core.vite.json @@ -0,0 +1,15 @@ +{ + "extends": "./tsconfig.json", + "compilerOptions": { + "paths": { + "@app/*": [ + "src/core/*" + ] + } + }, + "exclude": [ + "src/proprietary", + "src/desktop", + "node_modules" + ] +} diff --git a/frontend/tsconfig.desktop.json b/frontend/tsconfig.desktop.json index cb842c3991..679f0664f6 100644 --- a/frontend/tsconfig.desktop.json +++ b/frontend/tsconfig.desktop.json @@ -7,16 +7,15 @@ "src/proprietary/*", "src/core/*" ], - "@desktop/*": ["src/desktop/*"], "@proprietary/*": ["src/proprietary/*"], "@core/*": ["src/core/*"] } }, - "exclude": [ - "src/core/**/*.test.ts*", - "src/core/**/*.spec.ts*", - "src/proprietary/**/*.test.ts*", - "src/proprietary/**/*.spec.ts*", - "node_modules" + "include": [ + "src/*.js", + "src/*.ts", + "src/*.tsx", + "src/core/setupTests.ts", + "src/desktop" ] } diff --git a/frontend/tsconfig.desktop.vite.json b/frontend/tsconfig.desktop.vite.json new file mode 100644 index 0000000000..8d586f5bee --- /dev/null +++ b/frontend/tsconfig.desktop.vite.json @@ -0,0 +1,21 @@ +{ + "extends": "./tsconfig.proprietary.vite.json", + "compilerOptions": { + "paths": { + "@app/*": [ + "src/desktop/*", + "src/proprietary/*", + "src/core/*" + ], + "@proprietary/*": ["src/proprietary/*"], + "@core/*": ["src/core/*"] + } + }, + "exclude": [ + "src/core/**/*.test.ts*", + "src/core/**/*.spec.ts*", + "src/proprietary/**/*.test.ts*", + "src/proprietary/**/*.spec.ts*", + "node_modules" + ] +} diff --git a/frontend/tsconfig.json b/frontend/tsconfig.json index 4d151995b1..242f518f6b 100644 --- a/frontend/tsconfig.json +++ b/frontend/tsconfig.json @@ -36,7 +36,6 @@ "src/proprietary/*", "src/core/*" ], - "@desktop/*": ["src/desktop/*"], "@proprietary/*": ["src/proprietary/*"], "@core/*": ["src/core/*"] }, diff --git a/frontend/tsconfig.proprietary.json b/frontend/tsconfig.proprietary.json index 4c726f09a7..6b60d8bc0c 100644 --- a/frontend/tsconfig.proprietary.json +++ b/frontend/tsconfig.proprietary.json @@ -9,10 +9,11 @@ "@core/*": ["src/core/*"] } }, - "exclude": [ - "src/core/**/*.test.ts*", - "src/core/**/*.spec.ts*", - "src/desktop", - "node_modules" + "include": [ + "src/*.js", + "src/*.ts", + "src/*.tsx", + "src/core/setupTests.ts", + "src/proprietary" ] } diff --git a/frontend/tsconfig.proprietary.vite.json b/frontend/tsconfig.proprietary.vite.json new file mode 100644 index 0000000000..4c726f09a7 --- /dev/null +++ b/frontend/tsconfig.proprietary.vite.json @@ -0,0 +1,18 @@ +{ + "extends": "./tsconfig.json", + "compilerOptions": { + "paths": { + "@app/*": [ + "src/proprietary/*", + "src/core/*" + ], + "@core/*": ["src/core/*"] + } + }, + "exclude": [ + "src/core/**/*.test.ts*", + "src/core/**/*.spec.ts*", + "src/desktop", + "node_modules" + ] +} diff --git a/frontend/vite.config.ts b/frontend/vite.config.ts index ad269a19d4..de3dc20fc6 100644 --- a/frontend/vite.config.ts +++ b/frontend/vite.config.ts @@ -11,8 +11,8 @@ export default defineConfig(({ mode }) => { process.env.STIRLING_DESKTOP === 'true' || process.env.VITE_DESKTOP === 'true'; - const baseProject = isProprietary ? './tsconfig.proprietary.json' : './tsconfig.core.json'; - const desktopProject = isProprietary ? './tsconfig.desktop.json' : baseProject; + const baseProject = isProprietary ? './tsconfig.proprietary.vite.json' : './tsconfig.core.vite.json'; + const desktopProject = isProprietary ? './tsconfig.desktop.vite.json' : baseProject; const tsconfigProject = isDesktopMode ? desktopProject : baseProject; return { diff --git a/frontend/vitest.config.ts b/frontend/vitest.config.ts index 1110998964..6bfdf92dcb 100644 --- a/frontend/vitest.config.ts +++ b/frontend/vitest.config.ts @@ -37,7 +37,7 @@ export default defineConfig({ plugins: [ react(), tsconfigPaths({ - projects: ['./tsconfig.core.json'], + projects: ['./tsconfig.core.vite.json'], }), ], esbuild: { @@ -55,7 +55,7 @@ export default defineConfig({ plugins: [ react(), tsconfigPaths({ - projects: ['./tsconfig.proprietary.json'], + projects: ['./tsconfig.proprietary.vite.json'], }), ], esbuild: { @@ -73,7 +73,7 @@ export default defineConfig({ plugins: [ react(), tsconfigPaths({ - projects: ['./tsconfig.desktop.json'], + projects: ['./tsconfig.desktop.vite.json'], }), ], esbuild: { diff --git a/scripts/convert_cff_to_ttf.py b/scripts/convert_cff_to_ttf.py index fb4016b3e7..3b73e3e24d 100644 --- a/scripts/convert_cff_to_ttf.py +++ b/scripts/convert_cff_to_ttf.py @@ -509,18 +509,20 @@ Examples: # Positional arguments (backward compatibility): convert_cff_to_ttf.py font.cff font.otf mapping.tounicode - """ + """, ) # Add named arguments - parser.add_argument('--input', dest='input_file', help='Input CFF file path') - parser.add_argument('--output', dest='output_file', help='Output OTF file path') - parser.add_argument('--to-unicode', dest='tounicode_file', help='ToUnicode mapping file path') + parser.add_argument("--input", dest="input_file", help="Input CFF file path") + parser.add_argument("--output", dest="output_file", help="Output OTF file path") + parser.add_argument( + "--to-unicode", dest="tounicode_file", help="ToUnicode mapping file path" + ) # Add positional arguments for backward compatibility - parser.add_argument('input_pos', nargs='?', help='Input CFF file (positional)') - parser.add_argument('output_pos', nargs='?', help='Output OTF file (positional)') - parser.add_argument('tounicode_pos', nargs='?', help='ToUnicode file (positional)') + parser.add_argument("input_pos", nargs="?", help="Input CFF file (positional)") + parser.add_argument("output_pos", nargs="?", help="Output OTF file (positional)") + parser.add_argument("tounicode_pos", nargs="?", help="ToUnicode file (positional)") args = parser.parse_args()