Skip to content

Commit 02683f7

Browse files
authored
[Project Solar / Phase 1 / Engineering Follow-ups] Add localStorage parsing validation (#3401)
1 parent b5c7548 commit 02683f7

File tree

2 files changed

+63
-14
lines changed

2 files changed

+63
-14
lines changed

packages/components/src/services/hds-theming.ts

Lines changed: 47 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -65,6 +65,42 @@ export const HDS_THEMING_LOCALSTORAGE_DATA = 'hds-theming-data';
6565
export const DEFAULT_THEMING_OPTION_LIGHT_THEME = HdsModesLightValues.CdsG0;
6666
export const DEFAULT_THEMING_OPTION_DARK_THEME = HdsModesDarkValues.CdsG100;
6767

68+
type StoredThemingData = {
69+
theme: HdsThemes | undefined;
70+
options: HdsThemingOptions;
71+
};
72+
73+
// We use this guard function to check if the data parsed from `localStorage` conforms to the `StoredThemingData` type and so is safe to use.
74+
// This prevents the application from using corrupted, malformed or malicious data, by validating the object structure, theme, and mode values.
75+
76+
function isSafeStoredThemingData(data: unknown): data is StoredThemingData {
77+
if (typeof data !== 'object' || data === null) return false;
78+
79+
const d = data as Record<string, unknown>;
80+
81+
const isSafeThemeData =
82+
// there is no stored `theme` key in the object (eg. the `default` theme was selected)
83+
!('theme' in d) ||
84+
// there is a `theme` value and is one of the valid `HdsThemes`
85+
d['theme'] === undefined ||
86+
THEMES.includes(d['theme'] as HdsThemes);
87+
88+
const options = d['options'] as Record<string, unknown> | undefined;
89+
90+
const isSafeOptionsData =
91+
// there is no stored `options` key in the object (eg. it's the first run of the application)
92+
!('options' in d) ||
93+
// there is an `options` value and has valid entries
94+
(typeof options === 'object' &&
95+
options !== null &&
96+
'lightTheme' in options &&
97+
MODES_LIGHT.includes(options['lightTheme'] as HdsModesLight) &&
98+
'darkTheme' in options &&
99+
MODES_DARK.includes(options['darkTheme'] as HdsModesDark));
100+
101+
return isSafeThemeData && isSafeOptionsData;
102+
}
103+
68104
export default class HdsThemingService extends Service {
69105
@tracked _currentTheme: HdsThemes | undefined = undefined;
70106
@tracked _currentMode: HdsModes | undefined = undefined;
@@ -77,6 +113,7 @@ export default class HdsThemingService extends Service {
77113
const rawStoredThemingData = localStorage.getItem(
78114
HDS_THEMING_LOCALSTORAGE_DATA
79115
);
116+
80117
if (rawStoredThemingData !== null) {
81118
let storedThemingData: unknown;
82119
try {
@@ -87,16 +124,18 @@ export default class HdsThemingService extends Service {
87124
`Error while reading local storage '${HDS_THEMING_LOCALSTORAGE_DATA}' for theming`,
88125
error
89126
);
90-
storedThemingData = undefined;
91127
}
92-
if (storedThemingData) {
93-
const { theme, options } = storedThemingData as {
94-
theme: HdsThemes | undefined;
95-
options: HdsThemingOptions;
96-
};
128+
129+
if (isSafeStoredThemingData(storedThemingData)) {
130+
this.setTheme({
131+
theme: storedThemingData.theme,
132+
options: storedThemingData.options,
133+
});
134+
} else {
135+
// if data is not safe or malformed, reset theming to its defaults
97136
this.setTheme({
98-
theme,
99-
options,
137+
theme: undefined,
138+
options: undefined,
100139
});
101140
}
102141
}

showcase/app/services/shw-theming.ts

Lines changed: 16 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -9,11 +9,14 @@ import HdsThemingService from '@hashicorp/design-system-components/services/hds-
99
// HdsOnSetThemeCallbackArgs,
1010
// } from '@hashicorp/design-system-components/services/hds-theming';
1111

12-
export type ShwStylesheets =
13-
| 'standard'
14-
| 'css-selectors'
15-
| 'css-selectors--migration'
16-
| 'css-selectors--advanced';
12+
const STYLESHEETS = [
13+
'standard',
14+
'css-selectors',
15+
'css-selectors--migration',
16+
'css-selectors--advanced',
17+
] as const;
18+
19+
export type ShwStylesheets = (typeof STYLESHEETS)[number];
1720

1821
const ALL_STYLESHEETS_IDS: string[] = [
1922
'hds-components-stylesheet-default',
@@ -41,6 +44,10 @@ const STYLESHEETS_MAPPING: Record<ShwStylesheets, string[]> = {
4144

4245
const LOCALSTORAGE_CURRENT_STYLESHEET = 'shw-theming-current-stylesheet';
4346

47+
function isSafeStylesheetData(data: string): data is ShwStylesheets {
48+
return STYLESHEETS.includes(data as ShwStylesheets);
49+
}
50+
4451
export default class ShwThemingService extends HdsThemingService {
4552
@service declare readonly hdsTheming: HdsThemingService;
4653

@@ -52,8 +59,11 @@ export default class ShwThemingService extends HdsThemingService {
5259
const storedStylesheet = localStorage.getItem(
5360
LOCALSTORAGE_CURRENT_STYLESHEET,
5461
) as ShwStylesheets;
55-
if (storedStylesheet) {
62+
if (storedStylesheet && isSafeStylesheetData(storedStylesheet)) {
5663
this.setStylesheet(storedStylesheet);
64+
} else {
65+
// if data is not safe or malformed, reset stylesheet to its default
66+
this.setStylesheet('standard');
5767
}
5868
}
5969

0 commit comments

Comments
 (0)