fix(theme): reject malformed appearance fields

Co-authored-by: Kenny Lopez <klopez4212@gmail.com>
Signed-off-by: Kenny Lopez <klopez4212@gmail.com>
This commit is contained in:
Hardworking Honey
2026-08-12 13:41:46 +01:00
co-authored by Kenny Lopez
parent 7e06a5cb1c
commit 3809decb05
4 changed files with 51 additions and 10 deletions
@@ -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", () => {
@@ -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" ||
@@ -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 ||
@@ -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),