From 7a4a9ebd4c53b1565c24031cbdbc6bcadd87fcd3 Mon Sep 17 00:00:00 2001 From: Christiaan Scheermeijer Date: Fri, 30 Jul 2021 11:24:24 +0200 Subject: [PATCH] feat(auth): handle refresh token error --- src/containers/AccountModal/AccountModal.tsx | 21 ++++++- src/stores/AccountStore.ts | 58 ++++++++++++++++---- src/utils/persist.ts | 16 ++++-- 3 files changed, 78 insertions(+), 17 deletions(-) diff --git a/src/containers/AccountModal/AccountModal.tsx b/src/containers/AccountModal/AccountModal.tsx index 7e4f40258..8914e1a4b 100644 --- a/src/containers/AccountModal/AccountModal.tsx +++ b/src/containers/AccountModal/AccountModal.tsx @@ -4,9 +4,11 @@ import { useHistory } from 'react-router'; import { ConfigContext } from '../../providers/ConfigProvider'; import Dialog from '../../components/Dialog/Dialog'; import useQueryParam from '../../hooks/useQueryParam'; -import { removeQueryParam } from '../../utils/history'; +import { addQueryParam, removeQueryParam } from '../../utils/history'; import PaymentFailed from '../../components/PaymentFailed/PaymentFailed'; import Welcome from '../../components/Welcome/Welcome'; +import { AccountStore } from '../../stores/AccountStore'; +import LoadingOverlay from '../../components/LoadingOverlay/LoadingOverlay'; import styles from './AccountModal.module.scss'; import Login from './forms/Login'; @@ -15,17 +17,26 @@ import PersonalDetails from './forms/PersonalDetails'; import ChooseOffer from './forms/ChooseOffer'; import Checkout from './forms/Checkout'; +const PUBLIC_VIEWS = ['login', 'create-account', 'forgot-password', 'reset-password']; + const AccountModal = () => { const history = useHistory(); const viewParam = useQueryParam('u'); const [view, setView] = useState(viewParam); const message = useQueryParam('message'); + const { loading, auth } = AccountStore.useState((s) => s); useEffect(() => { // make sure the last view is rendered even when the modal gets closed if (viewParam) setView(viewParam); }, [viewParam]); + useEffect(() => { + if (!!viewParam && !loading && !auth && !PUBLIC_VIEWS.includes(viewParam)) { + history.push(addQueryParam(history, 'u', 'login')); + } + }, [viewParam, history, loading, auth]); + const { assets: { banner }, } = useContext(ConfigContext); @@ -35,6 +46,14 @@ const AccountModal = () => { }; const renderForm = () => { + if (!auth && loading) { + return ( +
+ +
+ ); + } + switch (view) { case 'login': return ; diff --git a/src/stores/AccountStore.ts b/src/stores/AccountStore.ts index f9d5e777a..458479e49 100644 --- a/src/stores/AccountStore.ts +++ b/src/stores/AccountStore.ts @@ -10,17 +10,28 @@ import { ConfigStore } from './ConfigStore'; const PERSIST_KEY_ACCOUNT = 'auth'; type AccountStore = { + loading: boolean; auth: AuthData | null; user: Customer | null; }; export const AccountStore = new Store({ + loading: true, auth: null, user: null, }); +const setLoading = (loading: boolean) => { + return AccountStore.update((s) => { + s.loading = loading; + }); +} + export const initializeAccount = async () => { const { config } = ConfigStore.getRawState(); + + if (!config.cleengId) setLoading(false); + const storedSession: AuthData | null = persist.getItem(PERSIST_KEY_ACCOUNT) as AuthData | null; let refreshTimeout: number; @@ -38,30 +49,41 @@ export const initializeAccount = async () => { ); // restore session from localStorage - if (storedSession) { - const refreshedAuthData = await getFreshJwtToken(config.cleengSandbox, storedSession); + try { + if (storedSession) { + const refreshedAuthData = await getFreshJwtToken(config.cleengSandbox, storedSession); - if (refreshedAuthData) { - await afterLogin(config.cleengSandbox, refreshedAuthData); + if (refreshedAuthData) { + await afterLogin(config.cleengSandbox, refreshedAuthData); + } } + } catch(error: unknown) { + await logout(); } + + setLoading(false); }; const getFreshJwtToken = async (sandbox: boolean, auth: AuthData) => { const result = await accountService.refreshToken({ refreshToken: auth.refreshToken }, sandbox); - if (result?.responseData) { - return result.responseData; - } + if (result.errors.length) throw new Error(result.errors[0]); + + return result?.responseData; }; const refreshJwtToken = async (sandbox: boolean, auth: AuthData) => { - const authData = await getFreshJwtToken(sandbox, auth); + try { + const authData = await getFreshJwtToken(sandbox, auth); - if (authData) { - AccountStore.update((s) => { - s.auth = { ...s.auth, ...authData }; - }); + if (authData) { + AccountStore.update((s) => { + s.auth = { ...s.auth, ...authData }; + }); + } + } catch(error: unknown) { + // failed to refresh, logout user + await logout(); } }; @@ -73,6 +95,7 @@ export const afterLogin = async (sandbox: boolean, auth: AuthData) => { if (response.errors.length) throw new Error(response.errors[0]); AccountStore.update((s) => { + s.loading = false; s.auth = auth; s.user = response.responseData; }); @@ -85,6 +108,8 @@ export const login = async (email: string, password: string) => { if (!cleengId) throw new Error('cleengId is not configured'); + setLoading(true); + const response = await accountService.login({ email, password, publisherId: cleengId }, cleengSandbox); if (response.errors.length > 0) throw new Error(response.errors[0]); @@ -92,6 +117,15 @@ export const login = async (email: string, password: string) => { return afterLogin(cleengSandbox, response.responseData); }; +export const logout = async () => { + persist.removeItem(PERSIST_KEY_ACCOUNT); + + AccountStore.update(s => { + s.auth = null; + s.user = null; + }); +}; + export const register = async (email: string, password: string) => { const { config: { cleengId, cleengSandbox }, diff --git a/src/utils/persist.ts b/src/utils/persist.ts index 05ed80089..98ad5a5e2 100644 --- a/src/utils/persist.ts +++ b/src/utils/persist.ts @@ -27,16 +27,24 @@ const getItem = (key: string) => { } }; +const removeItem = (key: string) => { + const storageKey = `${LOCAL_STORAGE_PREFIX}${key}`; + + try { + window.localStorage.removeItem(storageKey); + } catch (error: unknown) { + console.error(error); + } +}; + const parseJSON = (value?: string | null): unknown | undefined => { if (!value) return; try { - const parsedValue = JSON.parse(value); - - return parsedValue; + return JSON.parse(value); } catch (error: unknown) { return; } }; -export { setItem, getItem }; +export { setItem, getItem, removeItem };