Localstorage: Use `zod` to parse type `FormData`.

Local storage is an untyped interface external to the frontend code
itself. The `data` field after `JSON.parse`'d from `raw_data` can be
further validated using `zod`'s schema `formDataSchema`.

The test case `server_upgrade_alert hide_duration_expired` in
`navbar_alerts.test.js` has a bug at `start_time`, which is fixed in
this commit. `start_time` is a mock value of `Date.now()` used in
`localstorage.ts`, which will concatenate with a number `expires`.
So `start_time` was supposed to be an integer value. Before fix, `new
Date(1620327447050)` returns a `Date` object which is wrongly
concatenated with `expires`.

Fixes #24997.
This commit is contained in:
AcKindle3 2023-04-07 17:13:00 -04:00 committed by Tim Abbott
parent 70b8eb3423
commit fd84651a16
2 changed files with 24 additions and 16 deletions

View File

@ -1,10 +1,18 @@
import {z} from "zod";
import * as blueslip from "./blueslip";
type FormData = {
data: unknown;
__valid: true;
expires: number | null;
};
const formDataSchema = z
.object({
data: z.unknown(),
__valid: z.literal(true),
expires: z.number().nullable(),
})
// z.unknown by default marks the field as optional.
// Use zod transform to make optional data field non-optional.
.transform((o) => ({data: o.data, ...o}));
type FormData = z.infer<typeof formDataSchema>;
export type LocalStorage = {
setExpiry(expires: number, isGlobal: boolean): LocalStorage;
@ -53,18 +61,18 @@ const ls = {
return undefined;
}
data = JSON.parse(raw_data);
data = formDataSchema.parse(data);
if (
// JSON forms of data with `Infinity` turns into `null`,
// so if null then it hasn't expired since nothing was specified.
data.expires === null ||
!ls.isExpired(data.expires)
) {
return data;
}
} catch {
// data stays undefined
}
if (
data &&
data.__valid &&
// JSON forms of data with `Infinity` turns into `null`,
// so if null then it hasn't expired since nothing was specified.
(data.expires === null || !ls.isExpired(data.expires))
) {
return data;
}
return undefined;
},

View File

@ -91,7 +91,7 @@ test("profile_incomplete_alert", () => {
test("server_upgrade_alert hide_duration_expired", ({override}) => {
const ls = localstorage();
const start_time = new Date(1620327447050); // Thursday 06/5/2021 07:02:27 AM (UTC+0)
const start_time = 1620327447050; // Thursday 06/5/2021 07:02:27 AM (UTC+0)
override(Date, "now", () => start_time);
assert.equal(ls.get("lastUpgradeNagDismissalTime"), undefined);
@ -99,7 +99,7 @@ test("server_upgrade_alert hide_duration_expired", ({override}) => {
navbar_alerts.dismiss_upgrade_nag(ls);
assert.equal(navbar_alerts.should_show_server_upgrade_notification(ls), false);
override(Date, "now", () => addDays(start_time, 8)); // Friday 14/5/2021 07:02:27 AM (UTC+0)
override(Date, "now", () => addDays(start_time, 8).getTime()); // Friday 14/5/2021 07:02:27 AM (UTC+0)
assert.equal(navbar_alerts.should_show_server_upgrade_notification(ls), true);
navbar_alerts.dismiss_upgrade_nag(ls);
assert.equal(navbar_alerts.should_show_server_upgrade_notification(ls), false);