From 80230b3d7e9a97560af196937e799d3b878baffb Mon Sep 17 00:00:00 2001 From: grimsi <9295182+grimsi@users.noreply.github.com> Date: Sat, 17 May 2025 22:30:51 +0200 Subject: [PATCH] Fix excessive DB access in SecurityConfig Fix array parsing bug in ConfigService Potential fix for push subscription being cancelled in frontend Simplify ConfigState since we are already returning the correct types from the backend --- .../administration/UserManagement.tsx | 5 +- .../src/main/frontend/state/ConfigState.ts | 80 +++++-------------- .../grimsi/gameyfin/config/ConfigService.kt | 11 ++- .../gameyfin/core/security/SecurityConfig.kt | 3 +- 4 files changed, 32 insertions(+), 67 deletions(-) diff --git a/gameyfin/src/main/frontend/components/administration/UserManagement.tsx b/gameyfin/src/main/frontend/components/administration/UserManagement.tsx index d92365a..74a4a64 100644 --- a/gameyfin/src/main/frontend/components/administration/UserManagement.tsx +++ b/gameyfin/src/main/frontend/components/administration/UserManagement.tsx @@ -9,13 +9,10 @@ import {SmallInfoField} from "Frontend/components/general/SmallInfoField"; import {Info, UserPlus} from "@phosphor-icons/react"; import {Button, Divider, Tooltip, useDisclosure} from "@heroui/react"; import InviteUserModal from "Frontend/components/general/modals/InviteUserModal"; -import {useSnapshot} from "valtio/react"; -import {configState} from "Frontend/state/ConfigState"; function UserManagementLayout({getConfig, formik}: any) { const inviteUserModal = useDisclosure(); const [users, setUsers] = useState([]); - const config = useSnapshot(configState); useEffect(() => { UserEndpoint.getAllUsers().then( @@ -35,7 +32,7 @@ function UserManagementLayout({getConfig, formik}: any) {

Users

- {!config.configEntries["sso.oidc.auto-register-new-users"].value && + {!getConfig("sso.oidc.auto-register-new-users").value && } diff --git a/gameyfin/src/main/frontend/state/ConfigState.ts b/gameyfin/src/main/frontend/state/ConfigState.ts index 10fe55d..b554083 100644 --- a/gameyfin/src/main/frontend/state/ConfigState.ts +++ b/gameyfin/src/main/frontend/state/ConfigState.ts @@ -2,15 +2,19 @@ import {proxy} from 'valtio'; import ConfigEntryDto from "Frontend/generated/de/grimsi/gameyfin/config/dto/ConfigEntryDto"; import {ConfigEndpoint} from "Frontend/generated/endpoints"; import ConfigUpdateDto from "Frontend/generated/de/grimsi/gameyfin/config/dto/ConfigUpdateDto"; +import {Subscription} from "@vaadin/hilla-frontend"; type ConfigState = { + subscription?: Subscription; isLoaded: boolean; configEntries: Record; configNested: NestedConfig; }; export const configState = proxy({ - isLoaded: false, + get isLoaded() { + return this.subscription != null; + }, configEntries: {}, get configNested() { return toNestedConfig(Object.values(this.configEntries)); @@ -26,10 +30,9 @@ export async function initializeConfig() { initialEntries.forEach((entry) => { configState.configEntries[entry.key] = entry; }); - configState.isLoaded = true; // Subscribe to real-time updates - ConfigEndpoint.subscribe().onNext((updateDto: ConfigUpdateDto) => { + configState.subscription = ConfigEndpoint.subscribe().onNext((updateDto: ConfigUpdateDto) => { Object.entries(updateDto.updates).forEach(([key, value]) => { if (configState.configEntries[key]) { configState.configEntries[key].value = value; @@ -44,63 +47,24 @@ export type NestedConfig = { [field: string]: any; } -function toNestedConfig(configArray: ConfigEntryDto[]): NestedConfig { - const nestedConfig: NestedConfig = {}; +function toNestedConfig(entries: ConfigEntryDto[]): Record { + const result: Record = {}; - configArray.forEach(item => { - const keys = item.key!.split('.'); - let currentLevel = nestedConfig; + for (const entry of entries) { + const keys = entry.key.split('.'); + let current = result; - // Traverse the nested structure and create objects as needed - keys.forEach((key, index) => { - if (index === keys.length - 1) { - // Convert value to the appropriate type - let value: any; - switch (item.type) { - case 'Boolean': - value = typeof item.value == 'boolean' ? item.value : item.value === 'true'; - break; - case 'Int': - value = typeof item.value == 'number' ? item.value : 0; - break; - case 'Float': - value = typeof item.value == 'number' ? item.value : 0.0; - break; - case 'Array': - if (Array.isArray(item.value)) { - switch (item.elementType) { - case 'Boolean': - value = item.value.map(v => typeof v === 'boolean' ? v : v === 'true'); - break; - case 'Int': - case 'Integer': - value = item.value.map(v => typeof v == 'number' ? v : 0); - break; - case 'Float': - value = item.value.map(v => typeof v == 'number' ? v : 0.0); - break; - case 'String': - default: - value = item.value.map(v => v.toString()); - break; - } - } else { - value = []; - } - break; - case 'String': - default: - value = item.value; - break; - } - currentLevel[key] = value; + for (let i = 0; i < keys.length; i++) { + const key = keys[i]; + + if (i === keys.length - 1) { + current[key] = entry.value; } else { - if (!currentLevel[key]) { - currentLevel[key] = {}; - } - currentLevel = currentLevel[key]; + current[key] = current[key] || {}; + current = current[key]; } - }); - }); - return nestedConfig; + } + } + + return result; } \ No newline at end of file diff --git a/gameyfin/src/main/kotlin/de/grimsi/gameyfin/config/ConfigService.kt b/gameyfin/src/main/kotlin/de/grimsi/gameyfin/config/ConfigService.kt index 4fa8608..516ec60 100644 --- a/gameyfin/src/main/kotlin/de/grimsi/gameyfin/config/ConfigService.kt +++ b/gameyfin/src/main/kotlin/de/grimsi/gameyfin/config/ConfigService.kt @@ -97,9 +97,9 @@ class ConfigService( var configEntry = appConfigRepository.findByIdOrNull(key) val parsedValue = - if (value.javaClass.isArray) + if (value.javaClass.isArray) { (value as Array).joinToString(",") - else + } else value.toString() if (configEntry == null) { @@ -173,8 +173,11 @@ class ConfigService( configProperty.type.java.isArray -> { val componentType = configProperty.type.java.componentType // Remove the brackets and split the string by commas - val elements = value.removeSurrounding("[", "]").split(",") - if (elements.isEmpty()) return emptyArray() as T + val elements = value + .removeSurrounding("[", "]") + .split(",") + .filter { it.isNotBlank() } + when (componentType) { String::class.java -> elements.toTypedArray() as T Boolean::class.java -> elements.map { it.toBoolean() }.toTypedArray() as T diff --git a/gameyfin/src/main/kotlin/de/grimsi/gameyfin/core/security/SecurityConfig.kt b/gameyfin/src/main/kotlin/de/grimsi/gameyfin/core/security/SecurityConfig.kt index 5db5821..42f89b2 100644 --- a/gameyfin/src/main/kotlin/de/grimsi/gameyfin/core/security/SecurityConfig.kt +++ b/gameyfin/src/main/kotlin/de/grimsi/gameyfin/core/security/SecurityConfig.kt @@ -32,6 +32,7 @@ class SecurityConfig( ) : VaadinWebSecurity() { private val ssoProviderKey: String = "oidc" + private val allowedOrigins: List? = config.get(ConfigProperties.System.Cors.AllowedOrigins)?.toList() @Throws(Exception::class) override fun configure(http: HttpSecurity) { @@ -55,7 +56,7 @@ class SecurityConfig( http.cors { cors -> cors.configurationSource { request -> val configuration = CorsConfiguration() - configuration.allowedOrigins = config.get(ConfigProperties.System.Cors.AllowedOrigins)?.toList() + configuration.allowedOrigins = allowedOrigins configuration } }