mirror of
https://github.com/BrenBroZAYT/gameyfin.git
synced 2026-06-13 16:40:01 +00:00
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)
This commit is contained in:
@@ -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 }}
|
||||
|
||||
+1
-1
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "gameyfin",
|
||||
"version": "2.0.0.beta4",
|
||||
"version": "2.0.0.beta5",
|
||||
"type": "module",
|
||||
"dependencies": {
|
||||
"@heroui/react": "2.7.9",
|
||||
|
||||
@@ -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();
|
||||
}
|
||||
|
||||
@@ -38,7 +38,7 @@ function LibraryManagementLayout({getConfig, formik}: any) {
|
||||
return (
|
||||
<div className="flex flex-col">
|
||||
<Section title="Permissions"/>
|
||||
<ConfigFormField configElement={getConfig("library.allow-public-access")} isDisabled/>
|
||||
<ConfigFormField configElement={getConfig("library.allow-public-access")}/>
|
||||
|
||||
<Section title="Scanning"/>
|
||||
<div className="flex flex-col gap-4">
|
||||
|
||||
@@ -87,6 +87,8 @@ export default function EditGameMetadataModal({game, isOpen, onOpenChange}: Edit
|
||||
<ArrayInput key="features" name="features" label="Features"/>
|
||||
<ArrayInput key="perspectives" name="perspectives"
|
||||
label="Perspectives"/>
|
||||
<ArrayInput key="keywords" name="keywords"
|
||||
label="Keywords"/>
|
||||
</AccordionItem>
|
||||
</Accordion>
|
||||
</ModalBody>
|
||||
|
||||
@@ -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: <MainLayout/>,
|
||||
handle: {requiresLogin: true},
|
||||
handle: {requiresLogin: !ConfigEndpoint.isPublicAccessEnabled()},
|
||||
children: [
|
||||
{
|
||||
index: true, element: <HomeView/>
|
||||
@@ -64,6 +65,7 @@ export const {router, routes} = new RouterConfigurationBuilder()
|
||||
{
|
||||
path: 'administration',
|
||||
element: <AdministrationView/>,
|
||||
handle: {requiresLogin: true},
|
||||
children: [
|
||||
{
|
||||
path: 'libraries',
|
||||
|
||||
@@ -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() {
|
||||
<ScanProgressPopover/>
|
||||
</NavbarItem>
|
||||
}
|
||||
<NavbarItem>
|
||||
<ProfileMenu/>
|
||||
</NavbarItem>
|
||||
{auth.state.user &&
|
||||
<NavbarItem>
|
||||
<ProfileMenu/>
|
||||
</NavbarItem>
|
||||
}
|
||||
{!auth.state.user &&
|
||||
<NavbarItem>
|
||||
<Tooltip content="Sign in to your account" placement="bottom">
|
||||
<Button color="primary"
|
||||
radius="full"
|
||||
isIconOnly
|
||||
className="gradient-primary"
|
||||
onPress={() => navigate("/login")}>
|
||||
<SignIn fill="text-background/80"/>
|
||||
</Button>
|
||||
</Tooltip>
|
||||
</NavbarItem>
|
||||
}
|
||||
</NavbarContent>
|
||||
</Navbar>
|
||||
|
||||
|
||||
@@ -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<Set<string>>(new Set());
|
||||
const [selectedFeatures, setSelectedFeatures] = useState<Set<string>>(new Set());
|
||||
const [selectedPerspectives, setSelectedPerspectives] = useState<Set<string>>(new Set());
|
||||
const [selectedKeywords, setSelectedKeywords] = useState<Set<string>>(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("")}
|
||||
/>
|
||||
<div className="flex flex-row flex-wrap gap-2 justify-center">
|
||||
<div
|
||||
className="w-full justify-center"
|
||||
style={{
|
||||
display: "grid",
|
||||
gridTemplateColumns: "repeat(auto-fit, minmax(250px, 1fr))",
|
||||
gap: "0.5rem",
|
||||
margin: "0 auto"
|
||||
}}
|
||||
>
|
||||
<Select
|
||||
size="sm"
|
||||
className="max-w-xs"
|
||||
selectionMode="multiple"
|
||||
label="Libraries"
|
||||
placeholder="Filter by library"
|
||||
@@ -200,7 +224,6 @@ export default function SearchView() {
|
||||
</Select>
|
||||
<Select
|
||||
size="sm"
|
||||
className="max-w-xs"
|
||||
selectionMode="multiple"
|
||||
label="Developers"
|
||||
placeholder="Filter by developer"
|
||||
@@ -214,7 +237,6 @@ export default function SearchView() {
|
||||
</Select>
|
||||
<Select
|
||||
size="sm"
|
||||
className="max-w-xs"
|
||||
selectionMode="multiple"
|
||||
label="Genres"
|
||||
placeholder="Filter by genre"
|
||||
@@ -228,7 +250,6 @@ export default function SearchView() {
|
||||
</Select>
|
||||
<Select
|
||||
size="sm"
|
||||
className="max-w-xs"
|
||||
selectionMode="multiple"
|
||||
label="Themes"
|
||||
placeholder="Filter by theme"
|
||||
@@ -242,7 +263,6 @@ export default function SearchView() {
|
||||
</Select>
|
||||
<Select
|
||||
size="sm"
|
||||
className="max-w-xs"
|
||||
selectionMode="multiple"
|
||||
label="Features"
|
||||
placeholder="Filter by feature"
|
||||
@@ -256,7 +276,6 @@ export default function SearchView() {
|
||||
</Select>
|
||||
<Select
|
||||
size="sm"
|
||||
className="max-w-xs"
|
||||
selectionMode="multiple"
|
||||
label="Perspectives"
|
||||
placeholder="Filter by perspective"
|
||||
@@ -268,6 +287,19 @@ export default function SearchView() {
|
||||
<SelectItem key={perspective}>{toTitleCase(perspective)}</SelectItem>
|
||||
))}
|
||||
</Select>
|
||||
<Select
|
||||
size="sm"
|
||||
selectionMode="multiple"
|
||||
label="Keywords"
|
||||
placeholder="Filter by keyword"
|
||||
selectedKeys={selectedKeywords}
|
||||
//@ts-ignore
|
||||
onSelectionChange={setSelectedKeywords}
|
||||
>
|
||||
{Array.from(knownKeywords).map((keyword) => (
|
||||
<SelectItem key={keyword}>{keyword}</SelectItem>
|
||||
))}
|
||||
</Select>
|
||||
</div>
|
||||
<div className="mt-4 w-full px-4 select-none">
|
||||
<CoverGrid games={filteredGames}/>
|
||||
|
||||
@@ -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)
|
||||
}
|
||||
|
||||
@@ -17,7 +17,7 @@ sealed class ConfigProperties<T : Serializable>(
|
||||
data object AllowPublicAccess : ConfigProperties<Boolean>(
|
||||
Boolean::class,
|
||||
"library.allow-public-access",
|
||||
"Allow access to game libraries without login (coming soon™)",
|
||||
"Allow access to Gameyfin without login",
|
||||
false
|
||||
)
|
||||
|
||||
|
||||
@@ -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
|
||||
}
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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
|
||||
) {
|
||||
|
||||
@@ -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() }
|
||||
)
|
||||
}
|
||||
|
||||
@@ -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<String>?
|
||||
)
|
||||
+23
@@ -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<RequestAuthorizationContext> {
|
||||
|
||||
@Deprecated("Deprecated in superclass")
|
||||
override fun check(
|
||||
authentication: Supplier<Authentication?>?,
|
||||
`object`: RequestAuthorizationContext?
|
||||
): AuthorizationDecision? {
|
||||
val allow = config.get(ConfigProperties.Libraries.AllowPublicAccess) == true
|
||||
return AuthorizationDecision(allow)
|
||||
}
|
||||
}
|
||||
@@ -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<HttpSecurity>.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 ->
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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,
|
||||
|
||||
@@ -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<InputStreamResource>? {
|
||||
return getImageContent(id)
|
||||
}
|
||||
|
||||
@GetMapping("/header/{id}")
|
||||
fun getHeader(@PathVariable("id") id: Long): ResponseEntity<InputStreamResource>? {
|
||||
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
|
||||
|
||||
@@ -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()
|
||||
}
|
||||
|
||||
@@ -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<OidcUserAuthority>()
|
||||
.flatMap { oidcUserAuthority ->
|
||||
val userInfo = oidcUserAuthority.userInfo
|
||||
val roles = userInfo.getClaim<List<String>>("roles")
|
||||
val roles = userInfo.getClaim<List<String>>("roles") ?: return@flatMap emptySequence()
|
||||
roles.asSequence().mapNotNull {
|
||||
if (it.startsWith(SSO_ROLE_PREFIX)) SimpleGrantedAuthority(
|
||||
it.replace(SSO_ROLE_PREFIX, INTERNAL_ROLE_PREFIX)
|
||||
|
||||
@@ -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<UserInfoDto> {
|
||||
return userService.getAllUsers()
|
||||
|
||||
+1
-1
@@ -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 {
|
||||
|
||||
+3
-1
@@ -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
|
||||
|
||||
Reference in New Issue
Block a user