From 692e02e42fdf4bad5f026e5bd44b0830dccecb01 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 8f0a2c931..b03834688 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 358550be4..c109543cc 100644 --- a/utils/atoms/settings.ts +++ b/utils/atoms/settings.ts @@ -510,7 +510,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, ); @@ -519,7 +529,7 @@ export const useSettings = () => { const newSettings = { ...defaultValues, ..._settings, - ...update, + ...sanitizedUpdate, } as Settings; setSettings(newSettings); saveSettings(newSettings);