From 7f020120b355a0a398298718444d4137a24ff28b Mon Sep 17 00:00:00 2001 From: Gauvain Date: Wed, 10 Jun 2026 22:30:25 +0200 Subject: [PATCH] fix(settings): enforce admin-locked settings at write time updateSettings persisted any key into user storage, including ones the admin locked via the Streamyfin plugin. The read memo already overrides locked keys at runtime, but the write still landed in storage and several settings screens never disable their controls, so locked settings appeared changeable. Strip locked keys before persisting. --- .../settings/plugins/streamystats/page.tsx | 72 +++++++++++++------ utils/atoms/settings.ts | 14 +++- 2 files changed, 62 insertions(+), 24 deletions(-) diff --git a/app/(auth)/(tabs)/(home)/settings/plugins/streamystats/page.tsx b/app/(auth)/(tabs)/(home)/settings/plugins/streamystats/page.tsx index 8f0a2c93..b0383468 100644 --- a/app/(auth)/(tabs)/(home)/settings/plugins/streamystats/page.tsx +++ b/app/(auth)/(tabs)/(home)/settings/plugins/streamystats/page.tsx @@ -49,6 +49,15 @@ export default function StreamystatsPage() { ); const isUrlLocked = pluginSettings?.streamyStatsServerUrl?.locked === true; + const searchLocked = pluginSettings?.searchEngine?.locked === true; + const movieRecsLocked = + pluginSettings?.streamyStatsMovieRecommendations?.locked === true; + const seriesRecsLocked = + pluginSettings?.streamyStatsSeriesRecommendations?.locked === true; + const promotedWatchlistsLocked = + pluginSettings?.streamyStatsPromotedWatchlists?.locked === true; + const hideWatchlistsTabLocked = + pluginSettings?.hideWatchlistsTab?.locked === true; const isStreamystatsEnabled = !!url; const onSave = useCallback(() => { @@ -146,7 +155,9 @@ export default function StreamystatsPage() { placeholder={t( "home.settings.plugins.streamystats.server_url_placeholder", )} - value={url} + value={ + isUrlLocked ? (settings?.streamyStatsServerUrl ?? "") : url + } keyboardType='url' returnKeyType='done' autoCapitalize='none' @@ -171,11 +182,18 @@ export default function StreamystatsPage() { > + {/* Locked controls show the live admin value and can't be toggled — + local form state would let the switch flip while the write guard + drops the change. */} @@ -183,52 +201,62 @@ export default function StreamystatsPage() { title={t( "home.settings.plugins.streamystats.enable_movie_recommendations", )} - disabledByAdmin={ - pluginSettings?.streamyStatsMovieRecommendations?.locked === true - } + disabledByAdmin={movieRecsLocked} > diff --git a/utils/atoms/settings.ts b/utils/atoms/settings.ts index f4c6c7dd..2b8c7aca 100644 --- a/utils/atoms/settings.ts +++ b/utils/atoms/settings.ts @@ -504,7 +504,17 @@ export const useSettings = () => { if (!_settings) { return; } - const hasChanges = Object.entries(update).some( + // Admin-locked settings are enforced at write time too: a control that + // isn't disabled in the UI must not persist a value the admin pinned. + // The read memo already overrides locked keys, but without this guard the + // write would silently land in user storage and resurface once unlocked. + const sanitizedUpdate = Object.fromEntries( + Object.entries(update).filter( + ([key]) => pluginSettings?.[key as keyof Settings]?.locked !== true, + ), + ) as Partial; + + const hasChanges = Object.entries(sanitizedUpdate).some( ([key, value]) => _settings[key as keyof Settings] !== value, ); @@ -513,7 +523,7 @@ export const useSettings = () => { const newSettings = { ...defaultValues, ..._settings, - ...update, + ...sanitizedUpdate, } as Settings; setSettings(newSettings); saveSettings(newSettings);