From 7b61d319b1ff010f4d90e9e515881c04c7fc04d7 Mon Sep 17 00:00:00 2001 From: Alex Gleason Date: Fri, 9 Jul 2021 14:58:49 -0500 Subject: [PATCH 1/4] Auth: clear `me` when ME_FETCH_SKIP is dispatched --- app/soapbox/reducers/auth.js | 3 +++ 1 file changed, 3 insertions(+) diff --git a/app/soapbox/reducers/auth.js b/app/soapbox/reducers/auth.js index 5985939da..d1338c179 100644 --- a/app/soapbox/reducers/auth.js +++ b/app/soapbox/reducers/auth.js @@ -7,6 +7,7 @@ import { VERIFY_CREDENTIALS_SUCCESS, VERIFY_CREDENTIALS_FAIL, } from '../actions/auth'; +import { ME_FETCH_SKIP } from '../actions/me'; import { Map as ImmutableMap, fromJS } from 'immutable'; const defaultState = ImmutableMap({ @@ -158,6 +159,8 @@ const reducer = (state, action) => { return action.error.response.status === 403 ? deleteToken(state, action.token) : state; case SWITCH_ACCOUNT: return state.set('me', action.accountId); + case ME_FETCH_SKIP: + return state.set('me', null); default: return state; } From d3db2e37e300121bedfd081ad9f884aecbd7639a Mon Sep 17 00:00:00 2001 From: Alex Gleason Date: Fri, 9 Jul 2021 15:54:32 -0500 Subject: [PATCH 2/4] Auth: better data validation --- app/soapbox/reducers/auth.js | 37 +++++++++++++++++++++++++++--------- 1 file changed, 28 insertions(+), 9 deletions(-) diff --git a/app/soapbox/reducers/auth.js b/app/soapbox/reducers/auth.js index d1338c179..60b182604 100644 --- a/app/soapbox/reducers/auth.js +++ b/app/soapbox/reducers/auth.js @@ -8,7 +8,7 @@ import { VERIFY_CREDENTIALS_FAIL, } from '../actions/auth'; import { ME_FETCH_SKIP } from '../actions/me'; -import { Map as ImmutableMap, fromJS } from 'immutable'; +import { Map as ImmutableMap, List as ImmutableList, fromJS } from 'immutable'; const defaultState = ImmutableMap({ app: ImmutableMap(), @@ -17,31 +17,50 @@ const defaultState = ImmutableMap({ me: null, }); +const validId = id => typeof id === 'string' && id !== 'null' && id !== 'undefined'; + const getSessionUser = () => { const id = sessionStorage.getItem('soapbox:auth:me'); - if (id && typeof id === 'string' && id !== 'null' && id !== 'undefined') { - return id; - } else { - return undefined; - } + return validId(id) ? id : undefined; }; const sessionUser = getSessionUser(); const localState = fromJS(JSON.parse(localStorage.getItem('soapbox:auth'))); +// Checks if the user has an ID and access token +const validUser = user => { + try { + return validId(user.get('id')) && validId(user.get('access_token')); + } catch(e) { + return false; + } +}; + +// Finds the first valid user in the state +const firstValidUser = state => state.get('users', ImmutableMap()).find(validUser); + // If `me` doesn't match an existing user, attempt to shift it. const maybeShiftMe = state => { const users = state.get('users', ImmutableMap()); const me = state.get('me'); - if (!users.get(me)) { - return state.set('me', users.first(ImmutableMap()).get('id', null)); + if (!validUser(users.get(me))) { + const nextUser = firstValidUser(state); + return state.set('me', nextUser ? nextUser.get('id') : null); } else { return state; } }; -const setSessionUser = state => state.update('me', null, me => sessionUser || me); +// Set the user from the session or localStorage, whichever is valid first +const setSessionUser = state => state.update('me', null, me => { + const user = ImmutableList([ + state.getIn(['users', sessionUser]), + state.getIn(['users', me]), + ]).find(validUser); + + return user ? user.get('id') : null; +}); // Upgrade the initial state const migrateLegacy = state => { From 8cc8a465c7b670d7f405a59e995032872eb3684d Mon Sep 17 00:00:00 2001 From: Alex Gleason Date: Fri, 9 Jul 2021 16:24:18 -0500 Subject: [PATCH 3/4] Auth: sanitize the initial state --- app/soapbox/reducers/auth.js | 19 +++++++++++++++++++ 1 file changed, 19 insertions(+) diff --git a/app/soapbox/reducers/auth.js b/app/soapbox/reducers/auth.js index 60b182604..29bc8fcd8 100644 --- a/app/soapbox/reducers/auth.js +++ b/app/soapbox/reducers/auth.js @@ -83,6 +83,24 @@ const migrateLegacy = state => { }); }; +// Checks the state and makes it valid +const sanitizeState = state => { + return state.withMutations(state => { + // Remove invalid users, ensure ID match + state.update('users', ImmutableMap(), users => ( + users.filter((user, id) => ( + validUser(user) && user.get('id') === id + )) + )); + // Remove mismatched tokens + state.update('tokens', ImmutableMap(), tokens => ( + tokens.filter((token, id) => ( + validId(id) && token.get('access_token') === id + )) + )); + }); +}; + const persistAuth = state => localStorage.setItem('soapbox:auth', JSON.stringify(state.toJS())); const persistSession = state => { @@ -102,6 +120,7 @@ const initialize = state => { maybeShiftMe(state); setSessionUser(state); migrateLegacy(state); + sanitizeState(state); persistState(state); }); }; From 941e101fba2f8d3d4f861140c182a885401d8998 Mon Sep 17 00:00:00 2001 From: Alex Gleason Date: Fri, 9 Jul 2021 16:29:01 -0500 Subject: [PATCH 4/4] Auth: test ME_FETCH_SKIP in reducer --- app/soapbox/reducers/__tests__/auth-test.js | 10 ++++++++++ 1 file changed, 10 insertions(+) diff --git a/app/soapbox/reducers/__tests__/auth-test.js b/app/soapbox/reducers/__tests__/auth-test.js index a149042c4..77a9b1965 100644 --- a/app/soapbox/reducers/__tests__/auth-test.js +++ b/app/soapbox/reducers/__tests__/auth-test.js @@ -8,6 +8,7 @@ import { VERIFY_CREDENTIALS_FAIL, SWITCH_ACCOUNT, } from 'soapbox/actions/auth'; +import { ME_FETCH_SKIP } from 'soapbox/actions/me'; describe('auth reducer', () => { it('should return the initial state', () => { @@ -250,4 +251,13 @@ describe('auth reducer', () => { expect(result.get('me')).toEqual('5678'); }); }); + + describe('ME_FETCH_SKIP', () => { + it('sets `me` to null', () => { + const state = fromJS({ me: '1234' }); + const action = { type: ME_FETCH_SKIP }; + const result = reducer(state, action); + expect(result.get('me')).toEqual(null); + }); + }); });