From 443ba2ddc5b666a4fdfe4c1934ac0f11860a3172 Mon Sep 17 00:00:00 2001 From: Chris Bobbe Date: Tue, 28 Apr 2020 14:41:02 -0700 Subject: [PATCH] redux: Scaffolding for custom replace/revive logic. Be sure to use our SerializeEscaped wrapper, introduced earlier in this series, for proper escaping of the '__serializedType__' key, to prevent a security hole. This code looks almost exactly the same as it would without our wrapper. One key difference is that we use the constant SERIALIZED_TYPE_FIELD_NAME, exposed by our wrapper and used in its implementation, to be certain that we're using the same field name that gets escaped. Fortunately, this is very easy to do. (If we had used a different string, then, as long as that string were consistent between our custom replacer and reviver functions, (1) things would basically work, with no obvious indications that anything was wrong, and (2) the security hole would reopen.) --- src/boot/store.js | 28 ++++++++++++++++++++++++++++ 1 file changed, 28 insertions(+) diff --git a/src/boot/store.js b/src/boot/store.js index ddd7de05e2b..eb9e741bf68 100644 --- a/src/boot/store.js +++ b/src/boot/store.js @@ -1,6 +1,7 @@ /* @flow strict-local */ import { applyMiddleware, compose, createStore } from 'redux'; import type { Store } from 'redux'; +import Immutable from 'immutable'; import { persistStore, autoRehydrate } from '../third/redux-persist'; import type { Config } from '../third/redux-persist'; @@ -9,6 +10,9 @@ import rootReducer from './reducers'; import middleware from './middleware'; import ZulipAsyncStorage from './ZulipAsyncStorage'; import createMigration from '../redux-persist-migrate/index'; +import * as SerializeEscaped from './SerializeEscaped'; + +const { SERIALIZED_TYPE_FIELD_NAME } = SerializeEscaped; // AsyncStorage.clear(); // use to reset storage during development @@ -143,10 +147,34 @@ const migrations: { [string]: (GlobalState) => GlobalState } = { }), }; +const customReplacer = (key, value, defaultReplacer) => + // Scaffolding for the next commit, where we replace/revive ZulipVersion + defaultReplacer(key, value); +const customReviver = (key, value, defaultReviver) => { + if (value !== null && typeof value === 'object' && SERIALIZED_TYPE_FIELD_NAME in value) { + // Scaffolding for the next commit, where we replace/revive ZulipVersion + const data = value.data; // eslint-disable-line no-unused-vars + switch (value[SERIALIZED_TYPE_FIELD_NAME]) { + default: + // Fall back to defaultReviver, below + } + } + return defaultReviver(key, value); +}; + +const { stringify, parse } = SerializeEscaped.immutable( + Immutable, + null, + customReplacer, + customReviver, +); + const reduxPersistConfig: Config = { whitelist: [...storeKeys, ...cacheKeys], // $FlowFixMe: https://github.com/rt2zz/redux-persist/issues/823 storage: ZulipAsyncStorage, + serialize: stringify, + deserialize: parse, }; const store: Store = createStore(