From f523187971d9ce8b34c4c252eba402e3d2c3f89c Mon Sep 17 00:00:00 2001 From: grimsi <9295182+grimsi@users.noreply.github.com> Date: Wed, 24 Sep 2025 11:46:04 +0200 Subject: [PATCH 1/3] Update dependencies --- gradle.properties | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/gradle.properties b/gradle.properties index 7a18b27..e20e1b8 100644 --- a/gradle.properties +++ b/gradle.properties @@ -3,12 +3,12 @@ org.gradle.jvmargs=-Xmx2g -XX:MaxMetaspaceSize=512m org.gradle.parallel=true org.gradle.caching=true # Plugin versions -kotlinVersion=2.2.0 -kspVersion=2.2.0-2.0.2 -vaadinVersion=24.8.7 -springBootVersion=3.5.3 +kotlinVersion=2.2.20 +kspVersion=2.2.20-2.0.3 +vaadinVersion=24.9.0 +springBootVersion=3.5.6 springCloudVersion=2025.0.0 springDependencyManagementVersion=1.1.7 # Dependency versions pf4jVersion=3.13.0 -pf4jKspVersion=2.2.0-1.0.3 \ No newline at end of file +pf4jKspVersion=2.2.20-1.0.3 \ No newline at end of file From f43140eb2a9f661636087388d33922624210eace Mon Sep 17 00:00:00 2001 From: grimsi <9295182+grimsi@users.noreply.github.com> Date: Thu, 25 Sep 2025 13:18:36 +0200 Subject: [PATCH 2/3] Update SecurityConfig to use SecurityFilterChain --- .../app/core/security/SecurityConfig.kt | 75 ++++++++++--------- 1 file changed, 41 insertions(+), 34 deletions(-) diff --git a/app/src/main/kotlin/org/gameyfin/app/core/security/SecurityConfig.kt b/app/src/main/kotlin/org/gameyfin/app/core/security/SecurityConfig.kt index 23933d9..9edcba0 100644 --- a/app/src/main/kotlin/org/gameyfin/app/core/security/SecurityConfig.kt +++ b/app/src/main/kotlin/org/gameyfin/app/core/security/SecurityConfig.kt @@ -1,60 +1,69 @@ package org.gameyfin.app.core.security -import com.vaadin.flow.spring.security.VaadinWebSecurity +import com.vaadin.flow.spring.security.VaadinAwareSecurityContextHolderStrategyConfiguration +import com.vaadin.flow.spring.security.VaadinSecurityConfigurer +import com.vaadin.hilla.route.RouteUtil import org.gameyfin.app.config.ConfigProperties import org.gameyfin.app.config.ConfigService import org.springframework.context.annotation.Bean import org.springframework.context.annotation.Conditional import org.springframework.context.annotation.Configuration +import org.springframework.context.annotation.Import import org.springframework.core.env.Environment import org.springframework.http.HttpStatus import org.springframework.security.config.annotation.web.builders.HttpSecurity -import org.springframework.security.config.annotation.web.builders.WebSecurity import org.springframework.security.config.annotation.web.configuration.EnableWebSecurity -import org.springframework.security.config.annotation.web.configurers.AuthorizeHttpRequestsConfigurer import org.springframework.security.config.http.SessionCreationPolicy import org.springframework.security.core.session.SessionRegistry import org.springframework.security.oauth2.client.registration.ClientRegistration import org.springframework.security.oauth2.client.registration.ClientRegistrationRepository import org.springframework.security.oauth2.client.registration.InMemoryClientRegistrationRepository import org.springframework.security.oauth2.core.AuthorizationGrantType +import org.springframework.security.web.SecurityFilterChain import org.springframework.security.web.authentication.logout.HttpStatusReturningLogoutSuccessHandler @Configuration @EnableWebSecurity +@Import( + VaadinAwareSecurityContextHolderStrategyConfiguration::class +) class SecurityConfig( private val environment: Environment, private val config: ConfigService, private val ssoAuthenticationSuccessHandler: SsoAuthenticationSuccessHandler, private val sessionRegistry: SessionRegistry -) : VaadinWebSecurity() { +) { companion object { const val SSO_PROVIDER_KEY = "oidc" } - @Throws(Exception::class) - override fun configure(http: HttpSecurity) { - - // Configure your static resources with public access before calling super.configure(HttpSecurity) as it adds final anyRequest matcher - http.authorizeHttpRequests { auth: AuthorizeHttpRequestsConfigurer.AuthorizationManagerRequestMatcherRegistry -> - auth.requestMatchers("/login").permitAll() - .requestMatchers("/setup").permitAll() - .requestMatchers("/reset-password").permitAll() - .requestMatchers("/accept-invitation").permitAll() - .requestMatchers("/public/**").permitAll() - .requestMatchers("/images/**").permitAll() - .requestMatchers("/favicon.ico").permitAll() - .requestMatchers("/favicon.svg").permitAll() - - // Dynamic public access for certain endpoints - auth.requestMatchers("/").access(DynamicPublicAccessAuthorizationManager(config)) - .requestMatchers("/game/**").access(DynamicPublicAccessAuthorizationManager(config)) - .requestMatchers("/library/**").access(DynamicPublicAccessAuthorizationManager(config)) - .requestMatchers("/search/**").access(DynamicPublicAccessAuthorizationManager(config)) - .requestMatchers("/requests/**").access(DynamicPublicAccessAuthorizationManager(config)) - .requestMatchers("/download/**").access(DynamicPublicAccessAuthorizationManager(config)) + @Bean + fun filterChain(http: HttpSecurity, routeUtil: RouteUtil): SecurityFilterChain { + http.authorizeHttpRequests { auth -> + // Set default security policy that permits Hilla internal requests and denies all other + auth.requestMatchers(routeUtil::isRouteAllowed).permitAll() + // Gameyfin static resources and public endpoints + .requestMatchers( + "/login", + "/setup", + "/reset-password", + "/accept-invitation", + "/public/**", + "/images/**", + "/favicon.ico", + "/favicon.svg" + ).permitAll() + // Dynamic public access for certain endpoints + .requestMatchers( + "/", + "/game/**", + "/library/**", + "/search/**", + "/requests/**", + "/download/**" + ).access(DynamicPublicAccessAuthorizationManager(config)) } http.sessionManagement { sessionManagement -> @@ -67,9 +76,10 @@ class SecurityConfig( // Not needed since the frontend is served by the backend http.cors { cors -> cors.disable() } - super.configure(http) - - setLoginView(http, "/login", "/") + http.with(VaadinSecurityConfigurer.vaadin()) { configurer -> + // use a custom login view and redirect to root on logout + configurer.loginView("/login", "/") + } if (config.get(ConfigProperties.SSO.OIDC.Enabled) == true) { // Use custom success handler to handle user registration @@ -82,15 +92,12 @@ class SecurityConfig( exceptionHandling.authenticationEntryPoint(CustomAuthenticationEntryPoint()) } } - } - - @Throws(Exception::class) - public override fun configure(web: WebSecurity) { - super.configure(web) if ("dev" in environment.activeProfiles) { - web.ignoring().requestMatchers("/h2-console/**") + http.authorizeHttpRequests { auth -> auth.requestMatchers("/h2-console/**").permitAll() } } + + return http.build() } @Bean From 7cf227d3dd98de306acbc2fb2b0022ea8cb4d32e Mon Sep 17 00:00:00 2001 From: grimsi <9295182+grimsi@users.noreply.github.com> Date: Thu, 25 Sep 2025 14:25:40 +0200 Subject: [PATCH 3/3] Fix logout for SSO users --- app/src/main/frontend/components/ProfileMenu.tsx | 11 +---------- .../gameyfin/app/core/security/SecurityConfig.kt | 15 +++++++++++---- 2 files changed, 12 insertions(+), 14 deletions(-) diff --git a/app/src/main/frontend/components/ProfileMenu.tsx b/app/src/main/frontend/components/ProfileMenu.tsx index 71909ad..b6a8043 100644 --- a/app/src/main/frontend/components/ProfileMenu.tsx +++ b/app/src/main/frontend/components/ProfileMenu.tsx @@ -2,7 +2,6 @@ import {useAuth} from "Frontend/util/auth"; import {GearFine, Question, SignOut, User} from "@phosphor-icons/react"; import {Dropdown, DropdownItem, DropdownMenu, DropdownTrigger} from "@heroui/react"; import {useNavigate} from "react-router"; -import {ConfigEndpoint} from "Frontend/generated/endpoints"; import Avatar from "Frontend/components/general/Avatar"; import {CollectionElement} from "@react-types/shared"; import {isAdmin} from "Frontend/util/utils"; @@ -11,14 +10,6 @@ export default function ProfileMenu() { const auth = useAuth(); const navigate = useNavigate(); - async function logout() { - if (auth.state.user?.managedBySso) { - window.location.href = (await ConfigEndpoint.getSsoLogoutUrl()) || "/"; - } else { - await auth.logout(); - } - } - const profileMenuItems = [ { label: "My Profile", @@ -39,7 +30,7 @@ export default function ProfileMenu() { { label: "Sign Out", icon: , - onClick: logout, + onClick: auth.logout, color: "primary" }, ]; diff --git a/app/src/main/kotlin/org/gameyfin/app/core/security/SecurityConfig.kt b/app/src/main/kotlin/org/gameyfin/app/core/security/SecurityConfig.kt index 9edcba0..8397afa 100644 --- a/app/src/main/kotlin/org/gameyfin/app/core/security/SecurityConfig.kt +++ b/app/src/main/kotlin/org/gameyfin/app/core/security/SecurityConfig.kt @@ -76,12 +76,14 @@ class SecurityConfig( // Not needed since the frontend is served by the backend http.cors { cors -> cors.disable() } - http.with(VaadinSecurityConfigurer.vaadin()) { configurer -> - // use a custom login view and redirect to root on logout - configurer.loginView("/login", "/") - } if (config.get(ConfigProperties.SSO.OIDC.Enabled) == true) { + + http.with(VaadinSecurityConfigurer.vaadin()) { configurer -> + // Redirect to SSO provider on logout + configurer.loginView("/login", config.get(ConfigProperties.SSO.OIDC.LogoutUrl)) + } + // Use custom success handler to handle user registration http.oauth2Login { oauth2Login -> oauth2Login.successHandler(ssoAuthenticationSuccessHandler) } // Prevent unnecessary redirects @@ -91,6 +93,11 @@ class SecurityConfig( http.exceptionHandling { exceptionHandling -> exceptionHandling.authenticationEntryPoint(CustomAuthenticationEntryPoint()) } + } else { + // Use default Vaadin login URLs + http.with(VaadinSecurityConfigurer.vaadin()) { configurer -> + configurer.loginView("/login") + } } if ("dev" in environment.activeProfiles) {