From edf7a569dfc1416c6d51cc899880e44113123923 Mon Sep 17 00:00:00 2001 From: Simon <9295182+grimsi@users.noreply.github.com> Date: Wed, 16 Jul 2025 22:39:09 +0200 Subject: [PATCH] v2.0.0.beta5 (#626) * Fix wrong version property used in release.yml * Implement "Allow access to Gameyfin without login" * Implement filter by keyword (closes #613) * Fix bug where secret fields would be displayed as normal text * Optimize Gradle build performance * Fix ant path matchers * Fix NPE in role authority mapper (fixes #614) --- .github/workflows/release.yml | 8 +-- app/package.json | 2 +- .../main/frontend/components/ProfileMenu.tsx | 2 +- .../administration/LibraryManagement.tsx | 2 +- .../general/modals/EditGameMetadataModal.tsx | 2 + app/src/main/frontend/routes.tsx | 4 +- app/src/main/frontend/views/MainLayout.tsx | 23 +++++++-- app/src/main/frontend/views/SearchView.tsx | 50 +++++++++++++++---- .../org/gameyfin/app/config/ConfigEndpoint.kt | 17 +++++-- .../gameyfin/app/config/ConfigProperties.kt | 2 +- .../annotations/DynamicAccessInterceptor.kt | 17 ++++--- .../app/core/download/DownloadEndpoint.kt | 2 + .../core/download/DownloadProviderEndpoint.kt | 6 ++- .../app/core/plugins/PluginService.kt | 4 +- .../plugins/dto/PluginConfigMetadataDto.kt | 4 +- ...DynamicPublicAccessAuthorizationManager.kt | 23 +++++++++ .../app/core/security/SecurityConfig.kt | 9 +++- .../org/gameyfin/app/games/GameEndpoint.kt | 12 ++--- .../gameyfin/app/libraries/LibraryEndpoint.kt | 6 ++- .../org/gameyfin/app/media/ImageEndpoint.kt | 16 ++++-- .../org/gameyfin/app/system/SystemEndpoint.kt | 3 +- .../org/gameyfin/app/users/RoleService.kt | 4 +- .../org/gameyfin/app/users/UserEndpoint.kt | 10 ++-- build.gradle.kts | 2 +- gradle.properties | 4 +- 25 files changed, 168 insertions(+), 66 deletions(-) create mode 100644 app/src/main/kotlin/org/gameyfin/app/core/security/DynamicPublicAccessAuthorizationManager.kt diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml index 25cb530..5e2ab42 100644 --- a/.github/workflows/release.yml +++ b/.github/workflows/release.yml @@ -116,12 +116,12 @@ jobs: if: ${{ github.event.inputs.update_version }} uses: stefanzweifel/git-auto-commit-action@v6 with: - commit_message: 'chore: release v${{ needs.setup.outputs.release_version }}' - tagging_message: v${{ needs.setup.outputs.release_version }} + commit_message: 'chore: release v${{ github.event.inputs.version }}' + tagging_message: v${{ github.event.inputs.version }} - name: Detect prerelease id: detect_prerelease run: | - if [[ "${{ needs.setup.outputs.release_version }}" =~ ^[0-9]+\.[0-9]+\.[0-9]+$ ]]; then + if [[ "${{ github.event.inputs.version }}" =~ ^[0-9]+\.[0-9]+\.[0-9]+$ ]]; then echo "IS_PRERELEASE=false" >> $GITHUB_ENV echo "MAKE_LATEST=true" >> $GITHUB_ENV else @@ -132,6 +132,6 @@ jobs: if: ${{ github.event.inputs.update_version }} uses: softprops/action-gh-release@v2 with: - tag_name: v${{ needs.setup.outputs.release_version }} + tag_name: v${{ github.event.inputs.version }} prerelease: ${{ env.IS_PRERELEASE }} make_latest: ${{ env.MAKE_LATEST }} diff --git a/app/package.json b/app/package.json index 7311199..dc29495 100644 --- a/app/package.json +++ b/app/package.json @@ -1,6 +1,6 @@ { "name": "gameyfin", - "version": "2.0.0.beta4", + "version": "2.0.0.beta5", "type": "module", "dependencies": { "@heroui/react": "2.7.9", diff --git a/app/src/main/frontend/components/ProfileMenu.tsx b/app/src/main/frontend/components/ProfileMenu.tsx index cc2823d..71909ad 100644 --- a/app/src/main/frontend/components/ProfileMenu.tsx +++ b/app/src/main/frontend/components/ProfileMenu.tsx @@ -13,7 +13,7 @@ export default function ProfileMenu() { async function logout() { if (auth.state.user?.managedBySso) { - window.location.href = (await ConfigEndpoint.getLogoutUrl()) || "/"; + window.location.href = (await ConfigEndpoint.getSsoLogoutUrl()) || "/"; } else { await auth.logout(); } diff --git a/app/src/main/frontend/components/administration/LibraryManagement.tsx b/app/src/main/frontend/components/administration/LibraryManagement.tsx index 11fa2e8..14da06f 100644 --- a/app/src/main/frontend/components/administration/LibraryManagement.tsx +++ b/app/src/main/frontend/components/administration/LibraryManagement.tsx @@ -38,7 +38,7 @@ function LibraryManagementLayout({getConfig, formik}: any) { return (
- +
diff --git a/app/src/main/frontend/components/general/modals/EditGameMetadataModal.tsx b/app/src/main/frontend/components/general/modals/EditGameMetadataModal.tsx index b9eddd8..9cd774d 100644 --- a/app/src/main/frontend/components/general/modals/EditGameMetadataModal.tsx +++ b/app/src/main/frontend/components/general/modals/EditGameMetadataModal.tsx @@ -87,6 +87,8 @@ export default function EditGameMetadataModal({game, isOpen, onOpenChange}: Edit + diff --git a/app/src/main/frontend/routes.tsx b/app/src/main/frontend/routes.tsx index bc0cc12..a57c23c 100644 --- a/app/src/main/frontend/routes.tsx +++ b/app/src/main/frontend/routes.tsx @@ -23,6 +23,7 @@ import SearchView from "Frontend/views/SearchView"; import RecentlyAddedView from "Frontend/views/RecentlyAddedView"; import LibraryView from "Frontend/views/LibraryView"; import {RouterConfigurationBuilder} from "@vaadin/hilla-file-router/runtime.js"; +import {ConfigEndpoint} from "Frontend/generated/endpoints"; export const {router, routes} = new RouterConfigurationBuilder() .withReactRoutes([ @@ -32,7 +33,7 @@ export const {router, routes} = new RouterConfigurationBuilder() children: [ { element: , - handle: {requiresLogin: true}, + handle: {requiresLogin: !ConfigEndpoint.isPublicAccessEnabled()}, children: [ { index: true, element: @@ -64,6 +65,7 @@ export const {router, routes} = new RouterConfigurationBuilder() { path: 'administration', element: , + handle: {requiresLogin: true}, children: [ { path: 'libraries', diff --git a/app/src/main/frontend/views/MainLayout.tsx b/app/src/main/frontend/views/MainLayout.tsx index 5c9e8cd..516f28d 100644 --- a/app/src/main/frontend/views/MainLayout.tsx +++ b/app/src/main/frontend/views/MainLayout.tsx @@ -6,7 +6,7 @@ import GameyfinLogo from "Frontend/components/theming/GameyfinLogo"; import * as PackageJson from "../../../../package.json"; import {Outlet, useLocation, useNavigate} from "react-router"; import {useAuth} from "Frontend/util/auth"; -import {ArrowLeft, DiceSix, Heart, House, ListMagnifyingGlass} from "@phosphor-icons/react"; +import {ArrowLeft, DiceSix, Heart, House, ListMagnifyingGlass, SignIn} from "@phosphor-icons/react"; import Confetti, {ConfettiProps} from "react-confetti-boom"; import {useTheme} from "next-themes"; import {UserPreferenceService} from "Frontend/util/user-preference-service"; @@ -103,9 +103,24 @@ export default function MainLayout() { } - - - + {auth.state.user && + + + + } + {!auth.state.user && + + + + + + } diff --git a/app/src/main/frontend/views/SearchView.tsx b/app/src/main/frontend/views/SearchView.tsx index 261141b..a7c7074 100644 --- a/app/src/main/frontend/views/SearchView.tsx +++ b/app/src/main/frontend/views/SearchView.tsx @@ -18,6 +18,7 @@ export default function SearchView() { const knownThemes = useSnapshot(gameState).knownThemes; const knownFeatures = useSnapshot(gameState).knownFeatures; const knownPerspectives = useSnapshot(gameState).knownPerspectives; + const knownKeywords = useSnapshot(gameState).knownKeywords; const libraries = useSnapshot(libraryState).libraries as LibraryDto[]; const [searchParams, setSearchParams] = useSearchParams(); @@ -31,6 +32,7 @@ export default function SearchView() { const [selectedThemes, setSelectedThemes] = useState>(new Set()); const [selectedFeatures, setSelectedFeatures] = useState>(new Set()); const [selectedPerspectives, setSelectedPerspectives] = useState>(new Set()); + const [selectedKeywords, setSelectedKeywords] = useState>(new Set()); // Load initial filter values from URL parameters on component mount useEffect(() => { @@ -42,6 +44,7 @@ export default function SearchView() { const themes = searchParams.getAll("theme"); const features = searchParams.getAll("feature"); const perspectives = searchParams.getAll("perspective"); + const keywords = searchParams.getAll("keyword"); setSearchTerm(term); setSelectedLibraries(new Set(libs)); @@ -50,6 +53,7 @@ export default function SearchView() { setSelectedThemes(new Set(themes)); setSelectedFeatures(new Set(features)); setSelectedPerspectives(new Set(perspectives)); + setSelectedKeywords(new Set(keywords)); setInitialLoadComplete(true); }, []); @@ -102,15 +106,21 @@ export default function SearchView() { }); } + if (selectedKeywords.size > 0) { + selectedKeywords.forEach(keyword => { + newParams.append("keyword", keyword); + }); + } + setSearchParams(newParams, {replace: true}); }, [searchTerm, selectedLibraries, selectedDevelopers, selectedGenres, - selectedThemes, selectedFeatures, selectedPerspectives]); + selectedThemes, selectedFeatures, selectedPerspectives, selectedKeywords]); const filteredGames = useMemo(() => filterGames(), [ games, searchTerm, selectedLibraries, selectedDevelopers, selectedGenres, selectedThemes, - selectedFeatures, selectedPerspectives + selectedFeatures, selectedPerspectives, selectedKeywords ]); function filterGames(): GameDto[] { @@ -164,6 +174,13 @@ export default function SearchView() { ); } + // Apply keyword filter + if (selectedKeywords.size > 0) { + filtered = filtered.filter(game => + game.keywords?.some(keyword => selectedKeywords.has(keyword)) + ); + } + return filtered; } @@ -183,10 +200,17 @@ export default function SearchView() { onChange={(event) => setSearchTerm(event.target.value)} onClear={() => setSearchTerm("")} /> -
+
{toTitleCase(perspective)} ))} +
diff --git a/app/src/main/kotlin/org/gameyfin/app/config/ConfigEndpoint.kt b/app/src/main/kotlin/org/gameyfin/app/config/ConfigEndpoint.kt index 16d70c1..77281ba 100644 --- a/app/src/main/kotlin/org/gameyfin/app/config/ConfigEndpoint.kt +++ b/app/src/main/kotlin/org/gameyfin/app/config/ConfigEndpoint.kt @@ -1,5 +1,6 @@ package org.gameyfin.app.config +import com.vaadin.flow.server.auth.AnonymousAllowed import com.vaadin.hilla.Endpoint import io.github.oshai.kotlinlogging.KotlinLogging import jakarta.annotation.security.PermitAll @@ -7,6 +8,7 @@ import jakarta.annotation.security.RolesAllowed import org.gameyfin.app.config.dto.ConfigEntryDto import org.gameyfin.app.config.dto.ConfigUpdateDto import org.gameyfin.app.core.Role +import org.gameyfin.app.core.annotations.DynamicPublicAccess import org.gameyfin.app.users.UserService import org.gameyfin.app.users.util.isAdmin import reactor.core.publisher.Flux @@ -36,9 +38,16 @@ class ConfigEndpoint( /** Specific read-only endpoint for all users **/ - @PermitAll - fun isSsoEnabled(): Boolean? = configService.get(ConfigProperties.SSO.OIDC.Enabled) + @DynamicPublicAccess + @AnonymousAllowed + fun isSsoEnabled(): Boolean = configService.get(ConfigProperties.SSO.OIDC.Enabled) == true + + @DynamicPublicAccess + @AnonymousAllowed + fun getSsoLogoutUrl(): String? = configService.get(ConfigProperties.SSO.OIDC.LogoutUrl) + + @DynamicPublicAccess + @AnonymousAllowed + fun isPublicAccessEnabled(): Boolean = configService.get(ConfigProperties.Libraries.AllowPublicAccess) == true - @PermitAll - fun getLogoutUrl(): String? = configService.get(ConfigProperties.SSO.OIDC.LogoutUrl) } diff --git a/app/src/main/kotlin/org/gameyfin/app/config/ConfigProperties.kt b/app/src/main/kotlin/org/gameyfin/app/config/ConfigProperties.kt index b58bba4..3f90350 100644 --- a/app/src/main/kotlin/org/gameyfin/app/config/ConfigProperties.kt +++ b/app/src/main/kotlin/org/gameyfin/app/config/ConfigProperties.kt @@ -17,7 +17,7 @@ sealed class ConfigProperties( data object AllowPublicAccess : ConfigProperties( Boolean::class, "library.allow-public-access", - "Allow access to game libraries without login (coming soon™)", + "Allow access to Gameyfin without login", false ) diff --git a/app/src/main/kotlin/org/gameyfin/app/core/annotations/DynamicAccessInterceptor.kt b/app/src/main/kotlin/org/gameyfin/app/core/annotations/DynamicAccessInterceptor.kt index 8595a18..8dc4e05 100644 --- a/app/src/main/kotlin/org/gameyfin/app/core/annotations/DynamicAccessInterceptor.kt +++ b/app/src/main/kotlin/org/gameyfin/app/core/annotations/DynamicAccessInterceptor.kt @@ -1,16 +1,16 @@ package org.gameyfin.app.core.annotations -import org.gameyfin.app.config.ConfigService import jakarta.servlet.http.HttpServletRequest import jakarta.servlet.http.HttpServletResponse import org.gameyfin.app.config.ConfigProperties +import org.gameyfin.app.config.ConfigService import org.springframework.stereotype.Component import org.springframework.web.method.HandlerMethod import org.springframework.web.servlet.HandlerInterceptor @Component class DynamicAccessInterceptor( - private val configService: ConfigService + private val config: ConfigService ) : HandlerInterceptor { override fun preHandle( @@ -20,15 +20,16 @@ class DynamicAccessInterceptor( ): Boolean { val handlerMethod = (handler as? HandlerMethod) ?: return true val method = handlerMethod.method + val clazz = handlerMethod.beanType - // Check if method is annotated with @DynamicPublicAccess - if (method.isAnnotationPresent(DynamicPublicAccess::class.java)) { - // Check if user is authenticated or public access is enabled - if (request.userPrincipal != null || configService.get(ConfigProperties.Libraries.AllowPublicAccess) == true) { + val hasDynamicPublicAccess = + method.isAnnotationPresent(DynamicPublicAccess::class.java) || + clazz.isAnnotationPresent(DynamicPublicAccess::class.java) + + if (hasDynamicPublicAccess) { + if (request.userPrincipal != null || config.get(ConfigProperties.Libraries.AllowPublicAccess) == true) { return true } - - // Deny access if user is not logged in and public access is disabled response.status = HttpServletResponse.SC_UNAUTHORIZED return false } diff --git a/app/src/main/kotlin/org/gameyfin/app/core/download/DownloadEndpoint.kt b/app/src/main/kotlin/org/gameyfin/app/core/download/DownloadEndpoint.kt index b5f604d..9c788a4 100644 --- a/app/src/main/kotlin/org/gameyfin/app/core/download/DownloadEndpoint.kt +++ b/app/src/main/kotlin/org/gameyfin/app/core/download/DownloadEndpoint.kt @@ -1,5 +1,6 @@ package org.gameyfin.app.core.download +import com.vaadin.flow.server.auth.AnonymousAllowed import org.gameyfin.app.core.annotations.DynamicPublicAccess import org.gameyfin.app.games.GameService import org.gameyfin.pluginapi.download.FileDownload @@ -11,6 +12,7 @@ import org.springframework.web.servlet.mvc.method.annotation.StreamingResponseBo @RestController @RequestMapping("/download") @DynamicPublicAccess +@AnonymousAllowed class DownloadEndpoint( private val downloadService: DownloadService, private val gameService: GameService diff --git a/app/src/main/kotlin/org/gameyfin/app/core/download/DownloadProviderEndpoint.kt b/app/src/main/kotlin/org/gameyfin/app/core/download/DownloadProviderEndpoint.kt index 933b1f8..e9d3684 100644 --- a/app/src/main/kotlin/org/gameyfin/app/core/download/DownloadProviderEndpoint.kt +++ b/app/src/main/kotlin/org/gameyfin/app/core/download/DownloadProviderEndpoint.kt @@ -1,10 +1,12 @@ package org.gameyfin.app.core.download +import com.vaadin.flow.server.auth.AnonymousAllowed import com.vaadin.hilla.Endpoint -import jakarta.annotation.security.PermitAll +import org.gameyfin.app.core.annotations.DynamicPublicAccess @Endpoint -@PermitAll +@DynamicPublicAccess +@AnonymousAllowed class DownloadProviderEndpoint( private val downloadService: DownloadService ) { diff --git a/app/src/main/kotlin/org/gameyfin/app/core/plugins/PluginService.kt b/app/src/main/kotlin/org/gameyfin/app/core/plugins/PluginService.kt index bd15f64..c85b446 100644 --- a/app/src/main/kotlin/org/gameyfin/app/core/plugins/PluginService.kt +++ b/app/src/main/kotlin/org/gameyfin/app/core/plugins/PluginService.kt @@ -109,8 +109,8 @@ class PluginService( label = meta.label, description = meta.description, default = meta.default, - isSecret = meta.isSecret, - isRequired = meta.isRequired, + secret = meta.isSecret, + required = meta.isRequired, allowedValues = meta.allowedValues?.map { it.toString() } ) } diff --git a/app/src/main/kotlin/org/gameyfin/app/core/plugins/dto/PluginConfigMetadataDto.kt b/app/src/main/kotlin/org/gameyfin/app/core/plugins/dto/PluginConfigMetadataDto.kt index 5e7b1c6..d26d917 100644 --- a/app/src/main/kotlin/org/gameyfin/app/core/plugins/dto/PluginConfigMetadataDto.kt +++ b/app/src/main/kotlin/org/gameyfin/app/core/plugins/dto/PluginConfigMetadataDto.kt @@ -10,7 +10,7 @@ class PluginConfigMetadataDto( val label: String, val description: String, val default: Serializable?, - val isSecret: Boolean, - val isRequired: Boolean, + val secret: Boolean, + val required: Boolean, val allowedValues: List? ) \ No newline at end of file diff --git a/app/src/main/kotlin/org/gameyfin/app/core/security/DynamicPublicAccessAuthorizationManager.kt b/app/src/main/kotlin/org/gameyfin/app/core/security/DynamicPublicAccessAuthorizationManager.kt new file mode 100644 index 0000000..689f87d --- /dev/null +++ b/app/src/main/kotlin/org/gameyfin/app/core/security/DynamicPublicAccessAuthorizationManager.kt @@ -0,0 +1,23 @@ +package org.gameyfin.app.core.security + +import org.gameyfin.app.config.ConfigProperties +import org.gameyfin.app.config.ConfigService +import org.springframework.security.authorization.AuthorizationDecision +import org.springframework.security.authorization.AuthorizationManager +import org.springframework.security.core.Authentication +import org.springframework.security.web.access.intercept.RequestAuthorizationContext +import java.util.function.Supplier + +class DynamicPublicAccessAuthorizationManager( + private val config: ConfigService +) : AuthorizationManager { + + @Deprecated("Deprecated in superclass") + override fun check( + authentication: Supplier?, + `object`: RequestAuthorizationContext? + ): AuthorizationDecision? { + val allow = config.get(ConfigProperties.Libraries.AllowPublicAccess) == true + return AuthorizationDecision(allow) + } +} \ No newline at end of file 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 63885de..99f59bd 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 @@ -39,11 +39,18 @@ class SecurityConfig( // 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("/setup").permitAll() + auth.requestMatchers("/login").permitAll() + .requestMatchers("/setup").permitAll() .requestMatchers("/reset-password").permitAll() .requestMatchers("/accept-invitation").permitAll() .requestMatchers("/public/**").permitAll() .requestMatchers("/images/**").permitAll() + + // Dynamic public access for certain endpoints + auth.requestMatchers("/game/**").access(DynamicPublicAccessAuthorizationManager(config)) + .requestMatchers("/library/**").access(DynamicPublicAccessAuthorizationManager(config)) + .requestMatchers("/search/**").access(DynamicPublicAccessAuthorizationManager(config)) + .requestMatchers("/download/**").access(DynamicPublicAccessAuthorizationManager(config)) } http.sessionManagement { sessionManagement -> diff --git a/app/src/main/kotlin/org/gameyfin/app/games/GameEndpoint.kt b/app/src/main/kotlin/org/gameyfin/app/games/GameEndpoint.kt index 8901878..ec3ce56 100644 --- a/app/src/main/kotlin/org/gameyfin/app/games/GameEndpoint.kt +++ b/app/src/main/kotlin/org/gameyfin/app/games/GameEndpoint.kt @@ -1,20 +1,18 @@ package org.gameyfin.app.games +import com.vaadin.flow.server.auth.AnonymousAllowed import com.vaadin.hilla.Endpoint -import jakarta.annotation.security.PermitAll import jakarta.annotation.security.RolesAllowed import org.gameyfin.app.core.Role -import org.gameyfin.app.games.dto.GameDto -import org.gameyfin.app.games.dto.GameEvent -import org.gameyfin.app.games.dto.GameSearchResultDto -import org.gameyfin.app.games.dto.GameUpdateDto -import org.gameyfin.app.games.dto.OriginalIdDto +import org.gameyfin.app.core.annotations.DynamicPublicAccess +import org.gameyfin.app.games.dto.* import org.gameyfin.app.libraries.LibraryService import reactor.core.publisher.Flux import java.nio.file.Path @Endpoint -@PermitAll +@DynamicPublicAccess +@AnonymousAllowed class GameEndpoint( private val gameService: GameService, private val libraryService: LibraryService diff --git a/app/src/main/kotlin/org/gameyfin/app/libraries/LibraryEndpoint.kt b/app/src/main/kotlin/org/gameyfin/app/libraries/LibraryEndpoint.kt index 677906c..0e3476b 100644 --- a/app/src/main/kotlin/org/gameyfin/app/libraries/LibraryEndpoint.kt +++ b/app/src/main/kotlin/org/gameyfin/app/libraries/LibraryEndpoint.kt @@ -1,9 +1,10 @@ package org.gameyfin.app.libraries +import com.vaadin.flow.server.auth.AnonymousAllowed import com.vaadin.hilla.Endpoint -import jakarta.annotation.security.PermitAll import jakarta.annotation.security.RolesAllowed import org.gameyfin.app.core.Role +import org.gameyfin.app.core.annotations.DynamicPublicAccess import org.gameyfin.app.libraries.dto.LibraryDto import org.gameyfin.app.libraries.dto.LibraryEvent import org.gameyfin.app.libraries.dto.LibraryScanProgress @@ -14,7 +15,8 @@ import org.gameyfin.app.users.util.isAdmin import reactor.core.publisher.Flux @Endpoint -@PermitAll +@DynamicPublicAccess +@AnonymousAllowed class LibraryEndpoint( private val libraryService: LibraryService, private val userService: UserService, diff --git a/app/src/main/kotlin/org/gameyfin/app/media/ImageEndpoint.kt b/app/src/main/kotlin/org/gameyfin/app/media/ImageEndpoint.kt index 020cbb8..0d8b74b 100644 --- a/app/src/main/kotlin/org/gameyfin/app/media/ImageEndpoint.kt +++ b/app/src/main/kotlin/org/gameyfin/app/media/ImageEndpoint.kt @@ -1,13 +1,15 @@ package org.gameyfin.app.media -import org.gameyfin.app.core.plugins.PluginService -import org.gameyfin.app.games.entities.Image -import org.gameyfin.app.games.entities.ImageType -import org.gameyfin.app.users.UserService +import com.vaadin.flow.server.auth.AnonymousAllowed +import jakarta.annotation.security.PermitAll import jakarta.annotation.security.RolesAllowed import org.gameyfin.app.core.Role import org.gameyfin.app.core.Utils import org.gameyfin.app.core.annotations.DynamicPublicAccess +import org.gameyfin.app.core.plugins.PluginService +import org.gameyfin.app.games.entities.Image +import org.gameyfin.app.games.entities.ImageType +import org.gameyfin.app.users.UserService import org.springframework.core.io.ByteArrayResource import org.springframework.core.io.InputStreamResource import org.springframework.http.HttpHeaders @@ -18,9 +20,10 @@ import org.springframework.security.core.context.SecurityContextHolder import org.springframework.web.bind.annotation.* import org.springframework.web.multipart.MultipartFile -@DynamicPublicAccess @RestController @RequestMapping("/images") +@DynamicPublicAccess +@AnonymousAllowed class ImageEndpoint( private val imageService: ImageService, private val userService: UserService, @@ -36,6 +39,7 @@ class ImageEndpoint( fun getCover(@PathVariable("id") id: Long): ResponseEntity? { return getImageContent(id) } + @GetMapping("/header/{id}") fun getHeader(@PathVariable("id") id: Long): ResponseEntity? { return getImageContent(id) @@ -54,6 +58,7 @@ class ImageEndpoint( return getImageContent(avatar.id!!) } + @PermitAll @PostMapping("/avatar/upload") fun uploadAvatar(@RequestParam("file") file: MultipartFile) { val auth: Authentication = SecurityContextHolder.getContext().authentication @@ -68,6 +73,7 @@ class ImageEndpoint( userService.updateAvatar(auth.name, image) } + @PermitAll @PostMapping("/avatar/delete") fun deleteAvatar() { val auth: Authentication = SecurityContextHolder.getContext().authentication diff --git a/app/src/main/kotlin/org/gameyfin/app/system/SystemEndpoint.kt b/app/src/main/kotlin/org/gameyfin/app/system/SystemEndpoint.kt index 2e3e565..b2da29a 100644 --- a/app/src/main/kotlin/org/gameyfin/app/system/SystemEndpoint.kt +++ b/app/src/main/kotlin/org/gameyfin/app/system/SystemEndpoint.kt @@ -5,11 +5,10 @@ import jakarta.annotation.security.RolesAllowed import org.gameyfin.app.core.Role @Endpoint +@RolesAllowed(Role.Names.ADMIN) class SystemEndpoint( private val systemService: SystemService ) { - - @RolesAllowed(Role.Names.ADMIN) fun restart() { systemService.restart() } diff --git a/app/src/main/kotlin/org/gameyfin/app/users/RoleService.kt b/app/src/main/kotlin/org/gameyfin/app/users/RoleService.kt index 90dedc6..d5feeef 100644 --- a/app/src/main/kotlin/org/gameyfin/app/users/RoleService.kt +++ b/app/src/main/kotlin/org/gameyfin/app/users/RoleService.kt @@ -1,8 +1,8 @@ package org.gameyfin.app.users -import org.gameyfin.app.users.persistence.UserRepository import org.gameyfin.app.core.Role import org.gameyfin.app.users.entities.User +import org.gameyfin.app.users.persistence.UserRepository import org.springframework.security.core.Authentication import org.springframework.security.core.GrantedAuthority import org.springframework.security.core.authority.SimpleGrantedAuthority @@ -66,7 +66,7 @@ class RoleService( .filterIsInstance() .flatMap { oidcUserAuthority -> val userInfo = oidcUserAuthority.userInfo - val roles = userInfo.getClaim>("roles") + val roles = userInfo.getClaim>("roles") ?: return@flatMap emptySequence() roles.asSequence().mapNotNull { if (it.startsWith(SSO_ROLE_PREFIX)) SimpleGrantedAuthority( it.replace(SSO_ROLE_PREFIX, INTERNAL_ROLE_PREFIX) diff --git a/app/src/main/kotlin/org/gameyfin/app/users/UserEndpoint.kt b/app/src/main/kotlin/org/gameyfin/app/users/UserEndpoint.kt index 7b0411e..f208966 100644 --- a/app/src/main/kotlin/org/gameyfin/app/users/UserEndpoint.kt +++ b/app/src/main/kotlin/org/gameyfin/app/users/UserEndpoint.kt @@ -16,11 +16,6 @@ class UserEndpoint( private val userService: UserService, private val roleService: RoleService ) { - @PermitAll - fun existsByMail(email: String): Boolean { - return userService.existsByEmail(email) - } - @PermitAll fun getUserInfo(): UserInfoDto { return userService.getUserInfo() @@ -32,6 +27,11 @@ class UserEndpoint( userService.updateUser(auth.name, updates) } + @RolesAllowed(Role.Names.ADMIN) + fun existsByMail(email: String): Boolean { + return userService.existsByEmail(email) + } + @RolesAllowed(Role.Names.ADMIN) fun getAllUsers(): List { return userService.getAllUsers() diff --git a/build.gradle.kts b/build.gradle.kts index b8f0334..54a8f85 100644 --- a/build.gradle.kts +++ b/build.gradle.kts @@ -6,7 +6,7 @@ import org.jetbrains.kotlin.gradle.tasks.KotlinJvmCompile import java.nio.file.Files group = "org.gameyfin" -version = "2.0.0.beta4" +version = "2.0.0.beta5" allprojects { repositories { diff --git a/gradle.properties b/gradle.properties index 0b0d9d6..a6935cc 100644 --- a/gradle.properties +++ b/gradle.properties @@ -1,5 +1,7 @@ -# Increase Gradle metaspace size +# Gradle properties 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