maxdeviant/redux-persist-transform-encrypt

Issues using with redux-persist and AsyncStorage

austin-sprawls opened this issue · 24 comments

I'm using redux-persist and AsyncStorage to persist my redux state, but I'm running into issues using this library with them. I've attempted using both the sync and async libs, but both give me errors:

img_0047

img_0048

@austin-sprawls This looks like the same problem as in #13.

I see you posted two screenshots. Is the first one using the sync methods and the second one using the async methods?

@austin-sprawls The second screenshot's error message looks the same as the one in #10. I think the async implementation is not currently compatible with React Native (async support in general is still WIP).

Right, the second screenshot is the async method.

I tried replacing the json-stringify-safe call with JSON.stringify in the helper file, but it still errors "encrypt: expected outbound state to be a string"

@austin-sprawls I haven't worked with React Native before, but I will see if I can put together a repro using create-react-native-app.

I added an example React Native to help me reproduce the issue.

I'm running into this error though:

screen shot 2017-04-05 at 9 45 22 pm

@austin-sprawls Would you mind taking a look and seeing if you can help me reproduce your issue in the repo?

@austin-sprawls Okay, I got the example working, but I am not getting the same error.

Are you using any other redux-persist transforms?

Hi all,

It seems that makeAsyncDecryptor won't unpack values. So autoRehydrate throws promise in makeDecryptor. Very strange.

@askiiRobotics The async decrypt implementation is not working. But I think the sync methods should still work with ReactNative's AsyncStorage.

I'm seeing the same error message ("expected outbound state to be a string") using the sync implementation, using no other transforms and a very simple store. @austin-sprawls, did you make any progress?

I'm getting the same error here ("redux-persist-transform-encrypt: expected outbound state to be a string"), while using the sync implementation and I'm using the library version 1.0.2.
Also, I should mention that I'm using this with redux-persist-immutable.

@joshuajung @momosh Are both of you on React Native as well? I have not been able to reproduce this issue in React Native. You can refer to the example project to see if there are any differences.

A repo with a repro case in it would be most helpful.

I had the same issue.
I created a test repository for you.

https://github.com/thiagofernando/Persist

@thiagofernando Thanks! I'll have a look at it.

@maxdeviant yes, got this error while working with React Native. Also checked the example you linked, but could not make it work with redux-persist-immutable.
But, I've switched to redux-persist and everything works as expected.

Turn on the debug you can see better, in this new version of the react is not appearing the red screen, but the data is not storing.
Save a card, then reload. it will not be there.

I get the same error using react-native 0.45.1 and redux-persist-transform-encrypt 1.0.2 (the sync version):
expected outbound state to be a string
Did anyone find a solution for this?

@fdobre I've been meaning to look into this issue, but haven't had the time to do so.

Are you using any other transforms? Or just redux-persist-transform-encrypt?

No, I'm not using any other transforms:

const encryptor = createEncryptor({
  secretKey: 'my-super-secret-key-999',
});


persistStore(
  store,
  {
    storage: AsyncStorage,
    whitelist: ['auth'],
    transforms: [encryptor],
  },
);

and the auth state is something like this:

{
  user: {
     uid: 'kK6HGrd3nOMltYE6ltbocUp4WoU2',
     email: 'test@test.com'
  },
  token: '',
}

It seems like this is caused when you're trying to apply transform-encrypt onto a previously persisted un-encrypted store? I was running into this same issue until I purged the store and started afresh with encrypt on and it seems to be behaving right now. Could that be it?

Tried purge several times. It doesn't work on my side.

I also had this issue (also for a RN project, not using any other transforms), purging the store also worked for me. Not sure if that's indeed the issue (as @yogeshwar-20 suggested) but you could try this:

First remove the transform from the config and purge:
persistStore(store, { storage: AsyncStorage }).purge()

Reload your bundle, make sure you see the REHYDRATE action hitting the reducer, and its payload is NULL.

And then, add the transform back again and don't purge:
persistStore(store, { storage: AsyncStorage, transforms: [ encryptor ] })

An alternative solution, to prevent losing the stored state, would be

const version = 1

const migrations = {
	0: state => state, // the migration actually takes place in the transformer below
}

const encryptor = createEncryptor({
	secretKey: 'secret',
})

const transformer = createTransform(
	(inboundState, key) => {
		return inboundState
	},
	(outboundState, key, fullState) => {
-		if (fullState._persist.version === 0) {
+		if (version === 1) {
-			return AES.encrypt(outboundState, 'secret').toString()
+			return AES.encrypt(JSON.stringify(outboundState), 'secret').toString()
		}

		return outboundState
	},
	{},
)

const config = {
	...
	storage: AsyncStorage,
	version,
	migrate: createMigrate(migrations),
	transforms: [encryptor, transformer],
}

const reducer = persistReducer(config, rootReducer)

Here I assume that version 0 is unencrypted and I'm migrating to version 1 which is encrypted.

So, if the stored state is at version 0, the transformer encrypts it (with the same logic used by the plugin) and offers it to the next transform, which is the encryptor (decryptor in this case).

This would be more elegant if migrate ran before transforms, but that's not the case right now.

EDIT:
Well, sort of.
For some reason, after some testing fullState is kind of a hit and miss: sometimes it's there, sometimes it isn't, not sure what's going on.

So the check now is the version const.

Also, AES.encrypt wants a stringified object.

Closing due to age.