mirror of
https://github.com/BrenBroZAYT/gameyfin.git
synced 2026-06-16 16:20:04 +00:00
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
This commit is contained in:
@@ -9,13 +9,10 @@ import {SmallInfoField} from "Frontend/components/general/SmallInfoField";
|
|||||||
import {Info, UserPlus} from "@phosphor-icons/react";
|
import {Info, UserPlus} from "@phosphor-icons/react";
|
||||||
import {Button, Divider, Tooltip, useDisclosure} from "@heroui/react";
|
import {Button, Divider, Tooltip, useDisclosure} from "@heroui/react";
|
||||||
import InviteUserModal from "Frontend/components/general/modals/InviteUserModal";
|
import InviteUserModal from "Frontend/components/general/modals/InviteUserModal";
|
||||||
import {useSnapshot} from "valtio/react";
|
|
||||||
import {configState} from "Frontend/state/ConfigState";
|
|
||||||
|
|
||||||
function UserManagementLayout({getConfig, formik}: any) {
|
function UserManagementLayout({getConfig, formik}: any) {
|
||||||
const inviteUserModal = useDisclosure();
|
const inviteUserModal = useDisclosure();
|
||||||
const [users, setUsers] = useState<UserInfoDto[]>([]);
|
const [users, setUsers] = useState<UserInfoDto[]>([]);
|
||||||
const config = useSnapshot(configState);
|
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
UserEndpoint.getAllUsers().then(
|
UserEndpoint.getAllUsers().then(
|
||||||
@@ -35,7 +32,7 @@ function UserManagementLayout({getConfig, formik}: any) {
|
|||||||
|
|
||||||
<div className="flex flex-row items-baseline justify-between">
|
<div className="flex flex-row items-baseline justify-between">
|
||||||
<h2 className="text-xl font-bold mt-8 mb-1">Users</h2>
|
<h2 className="text-xl font-bold mt-8 mb-1">Users</h2>
|
||||||
{!config.configEntries["sso.oidc.auto-register-new-users"].value &&
|
{!getConfig("sso.oidc.auto-register-new-users").value &&
|
||||||
<SmallInfoField className="mb-4 text-warning" icon={Info}
|
<SmallInfoField className="mb-4 text-warning" icon={Info}
|
||||||
message="Automatic user registration for SSO users is disabled"/>
|
message="Automatic user registration for SSO users is disabled"/>
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -2,15 +2,19 @@ import {proxy} from 'valtio';
|
|||||||
import ConfigEntryDto from "Frontend/generated/de/grimsi/gameyfin/config/dto/ConfigEntryDto";
|
import ConfigEntryDto from "Frontend/generated/de/grimsi/gameyfin/config/dto/ConfigEntryDto";
|
||||||
import {ConfigEndpoint} from "Frontend/generated/endpoints";
|
import {ConfigEndpoint} from "Frontend/generated/endpoints";
|
||||||
import ConfigUpdateDto from "Frontend/generated/de/grimsi/gameyfin/config/dto/ConfigUpdateDto";
|
import ConfigUpdateDto from "Frontend/generated/de/grimsi/gameyfin/config/dto/ConfigUpdateDto";
|
||||||
|
import {Subscription} from "@vaadin/hilla-frontend";
|
||||||
|
|
||||||
type ConfigState = {
|
type ConfigState = {
|
||||||
|
subscription?: Subscription<ConfigUpdateDto>;
|
||||||
isLoaded: boolean;
|
isLoaded: boolean;
|
||||||
configEntries: Record<string, ConfigEntryDto>;
|
configEntries: Record<string, ConfigEntryDto>;
|
||||||
configNested: NestedConfig;
|
configNested: NestedConfig;
|
||||||
};
|
};
|
||||||
|
|
||||||
export const configState = proxy<ConfigState>({
|
export const configState = proxy<ConfigState>({
|
||||||
isLoaded: false,
|
get isLoaded() {
|
||||||
|
return this.subscription != null;
|
||||||
|
},
|
||||||
configEntries: {},
|
configEntries: {},
|
||||||
get configNested() {
|
get configNested() {
|
||||||
return toNestedConfig(Object.values(this.configEntries));
|
return toNestedConfig(Object.values(this.configEntries));
|
||||||
@@ -26,10 +30,9 @@ export async function initializeConfig() {
|
|||||||
initialEntries.forEach((entry) => {
|
initialEntries.forEach((entry) => {
|
||||||
configState.configEntries[entry.key] = entry;
|
configState.configEntries[entry.key] = entry;
|
||||||
});
|
});
|
||||||
configState.isLoaded = true;
|
|
||||||
|
|
||||||
// Subscribe to real-time updates
|
// Subscribe to real-time updates
|
||||||
ConfigEndpoint.subscribe().onNext((updateDto: ConfigUpdateDto) => {
|
configState.subscription = ConfigEndpoint.subscribe().onNext((updateDto: ConfigUpdateDto) => {
|
||||||
Object.entries(updateDto.updates).forEach(([key, value]) => {
|
Object.entries(updateDto.updates).forEach(([key, value]) => {
|
||||||
if (configState.configEntries[key]) {
|
if (configState.configEntries[key]) {
|
||||||
configState.configEntries[key].value = value;
|
configState.configEntries[key].value = value;
|
||||||
@@ -44,63 +47,24 @@ export type NestedConfig = {
|
|||||||
[field: string]: any;
|
[field: string]: any;
|
||||||
}
|
}
|
||||||
|
|
||||||
function toNestedConfig(configArray: ConfigEntryDto[]): NestedConfig {
|
function toNestedConfig(entries: ConfigEntryDto[]): Record<string, any> {
|
||||||
const nestedConfig: NestedConfig = {};
|
const result: Record<string, any> = {};
|
||||||
|
|
||||||
configArray.forEach(item => {
|
for (const entry of entries) {
|
||||||
const keys = item.key!.split('.');
|
const keys = entry.key.split('.');
|
||||||
let currentLevel = nestedConfig;
|
let current = result;
|
||||||
|
|
||||||
// Traverse the nested structure and create objects as needed
|
for (let i = 0; i < keys.length; i++) {
|
||||||
keys.forEach((key, index) => {
|
const key = keys[i];
|
||||||
if (index === keys.length - 1) {
|
|
||||||
// Convert value to the appropriate type
|
if (i === keys.length - 1) {
|
||||||
let value: any;
|
current[key] = entry.value;
|
||||||
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;
|
|
||||||
} else {
|
} else {
|
||||||
if (!currentLevel[key]) {
|
current[key] = current[key] || {};
|
||||||
currentLevel[key] = {};
|
current = current[key];
|
||||||
}
|
|
||||||
currentLevel = currentLevel[key];
|
|
||||||
}
|
}
|
||||||
});
|
}
|
||||||
});
|
}
|
||||||
return nestedConfig;
|
|
||||||
|
return result;
|
||||||
}
|
}
|
||||||
@@ -97,9 +97,9 @@ class ConfigService(
|
|||||||
var configEntry = appConfigRepository.findByIdOrNull(key)
|
var configEntry = appConfigRepository.findByIdOrNull(key)
|
||||||
|
|
||||||
val parsedValue =
|
val parsedValue =
|
||||||
if (value.javaClass.isArray)
|
if (value.javaClass.isArray) {
|
||||||
(value as Array<Serializable>).joinToString(",")
|
(value as Array<Serializable>).joinToString(",")
|
||||||
else
|
} else
|
||||||
value.toString()
|
value.toString()
|
||||||
|
|
||||||
if (configEntry == null) {
|
if (configEntry == null) {
|
||||||
@@ -173,8 +173,11 @@ class ConfigService(
|
|||||||
configProperty.type.java.isArray -> {
|
configProperty.type.java.isArray -> {
|
||||||
val componentType = configProperty.type.java.componentType
|
val componentType = configProperty.type.java.componentType
|
||||||
// Remove the brackets and split the string by commas
|
// Remove the brackets and split the string by commas
|
||||||
val elements = value.removeSurrounding("[", "]").split(",")
|
val elements = value
|
||||||
if (elements.isEmpty()) return emptyArray<Serializable>() as T
|
.removeSurrounding("[", "]")
|
||||||
|
.split(",")
|
||||||
|
.filter { it.isNotBlank() }
|
||||||
|
|
||||||
when (componentType) {
|
when (componentType) {
|
||||||
String::class.java -> elements.toTypedArray() as T
|
String::class.java -> elements.toTypedArray() as T
|
||||||
Boolean::class.java -> elements.map { it.toBoolean() }.toTypedArray() as T
|
Boolean::class.java -> elements.map { it.toBoolean() }.toTypedArray() as T
|
||||||
|
|||||||
@@ -32,6 +32,7 @@ class SecurityConfig(
|
|||||||
) : VaadinWebSecurity() {
|
) : VaadinWebSecurity() {
|
||||||
|
|
||||||
private val ssoProviderKey: String = "oidc"
|
private val ssoProviderKey: String = "oidc"
|
||||||
|
private val allowedOrigins: List<String>? = config.get(ConfigProperties.System.Cors.AllowedOrigins)?.toList()
|
||||||
|
|
||||||
@Throws(Exception::class)
|
@Throws(Exception::class)
|
||||||
override fun configure(http: HttpSecurity) {
|
override fun configure(http: HttpSecurity) {
|
||||||
@@ -55,7 +56,7 @@ class SecurityConfig(
|
|||||||
http.cors { cors ->
|
http.cors { cors ->
|
||||||
cors.configurationSource { request ->
|
cors.configurationSource { request ->
|
||||||
val configuration = CorsConfiguration()
|
val configuration = CorsConfiguration()
|
||||||
configuration.allowedOrigins = config.get(ConfigProperties.System.Cors.AllowedOrigins)?.toList()
|
configuration.allowedOrigins = allowedOrigins
|
||||||
configuration
|
configuration
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user