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 23933d9..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 @@ -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,11 +76,14 @@ class SecurityConfig( // Not needed since the frontend is served by the backend http.cors { cors -> cors.disable() } - super.configure(http) - - setLoginView(http, "/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 @@ -81,16 +93,18 @@ class SecurityConfig( http.exceptionHandling { exceptionHandling -> exceptionHandling.authenticationEntryPoint(CustomAuthenticationEntryPoint()) } + } else { + // Use default Vaadin login URLs + http.with(VaadinSecurityConfigurer.vaadin()) { configurer -> + configurer.loginView("/login") + } } - } - - @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 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