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