From 3809decb0593a2d0007ed0342f7ef77752f8979d Mon Sep 17 00:00:00 2001 From: Hardworking Honey Date: Wed, 12 Aug 2026 13:41:46 +0100 Subject: [PATCH] fix(theme): reject malformed appearance fields Co-authored-by: Kenny Lopez Signed-off-by: Kenny Lopez --- .../theme/communityThemePreference.test.mjs | 12 +++++++++++ .../shared/theme/communityThemePreference.ts | 14 ++++++++----- .../theme/community_theme_preference.dart | 14 ++++++++----- .../community_theme_preference_test.dart | 21 +++++++++++++++++++ 4 files changed, 51 insertions(+), 10 deletions(-) diff --git a/desktop/src/shared/theme/communityThemePreference.test.mjs b/desktop/src/shared/theme/communityThemePreference.test.mjs index d86c4cc1a..70a5803a4 100644 --- a/desktop/src/shared/theme/communityThemePreference.test.mjs +++ b/desktop/src/shared/theme/communityThemePreference.test.mjs @@ -62,6 +62,18 @@ test("parses only the versioned stable appearance contract", () => { parseCommunityThemePreference({ ...valid, prominentActiveTab: 1 }), null, ); + assert.equal( + parseCommunityThemePreference({ ...valid, glassBackground: null }), + null, + ); + assert.equal( + parseCommunityThemePreference({ ...valid, glassOpacity: null }), + null, + ); + assert.equal( + parseCommunityThemePreference({ ...valid, prominentActiveTab: null }), + null, + ); }); test("older theme records inherit the pre-migration appearance controls", () => { diff --git a/desktop/src/shared/theme/communityThemePreference.ts b/desktop/src/shared/theme/communityThemePreference.ts index 9c97f40a9..eed642e04 100644 --- a/desktop/src/shared/theme/communityThemePreference.ts +++ b/desktop/src/shared/theme/communityThemePreference.ts @@ -61,11 +61,15 @@ export function parseCommunityThemePreference( // These fields were added to the existing v1 payload. Fill older records // from the pre-migration appearance so a user's former global glass and tab // choices become the initial values for each existing community. - const glassBackground = - candidate.glassBackground ?? legacyFallback.glassBackground; - const glassOpacity = candidate.glassOpacity ?? legacyFallback.glassOpacity; - const prominentActiveTab = - candidate.prominentActiveTab ?? legacyFallback.prominentActiveTab; + const glassBackground = Object.hasOwn(candidate, "glassBackground") + ? candidate.glassBackground + : legacyFallback.glassBackground; + const glassOpacity = Object.hasOwn(candidate, "glassOpacity") + ? candidate.glassOpacity + : legacyFallback.glassOpacity; + const prominentActiveTab = Object.hasOwn(candidate, "prominentActiveTab") + ? candidate.prominentActiveTab + : legacyFallback.prominentActiveTab; if ( candidate.version !== 1 || typeof candidate.theme !== "string" || diff --git a/mobile/lib/shared/theme/community_theme_preference.dart b/mobile/lib/shared/theme/community_theme_preference.dart index ad4b1ea68..ae9e9d75b 100644 --- a/mobile/lib/shared/theme/community_theme_preference.dart +++ b/mobile/lib/shared/theme/community_theme_preference.dart @@ -49,11 +49,15 @@ class CommunityThemePreference { json['followSystem'] is! bool) { throw const FormatException('Invalid community theme preference'); } - final glassBackground = - json['glassBackground'] ?? defaultCommunityGlassBackground; - final glassOpacity = json['glassOpacity'] ?? defaultCommunityGlassOpacity; - final prominentActiveTab = - json['prominentActiveTab'] ?? defaultCommunityProminentActiveTab; + final glassBackground = json.containsKey('glassBackground') + ? json['glassBackground'] + : defaultCommunityGlassBackground; + final glassOpacity = json.containsKey('glassOpacity') + ? json['glassOpacity'] + : defaultCommunityGlassOpacity; + final prominentActiveTab = json.containsKey('prominentActiveTab') + ? json['prominentActiveTab'] + : defaultCommunityProminentActiveTab; if (glassBackground is! bool || glassOpacity is! num || !glassOpacity.isFinite || diff --git a/mobile/test/shared/theme/community_theme_preference_test.dart b/mobile/test/shared/theme/community_theme_preference_test.dart index b823dd324..1507e3064 100644 --- a/mobile/test/shared/theme/community_theme_preference_test.dart +++ b/mobile/test/shared/theme/community_theme_preference_test.dart @@ -79,6 +79,27 @@ void main() { 'followSystem': true, 'prominentActiveTab': 1, }, + { + 'version': 1, + 'theme': 'buzz', + 'accent': '#3b82f6', + 'followSystem': true, + 'glassBackground': null, + }, + { + 'version': 1, + 'theme': 'buzz', + 'accent': '#3b82f6', + 'followSystem': true, + 'glassOpacity': null, + }, + { + 'version': 1, + 'theme': 'buzz', + 'accent': '#3b82f6', + 'followSystem': true, + 'prominentActiveTab': null, + }, ]) { expect( () => CommunityThemePreference.fromJson(payload),