Skip to content

Commit

Permalink
fix(SSR): fixed management of token
Browse files Browse the repository at this point in the history
  • Loading branch information
emile-bex committed Jun 7, 2021
1 parent ef2631b commit d8ffa69
Show file tree
Hide file tree
Showing 18 changed files with 113 additions and 78 deletions.
4 changes: 2 additions & 2 deletions src/adapters/gateways/HTTPFeedGateway.ts
Original file line number Diff line number Diff line change
Expand Up @@ -23,7 +23,7 @@ export class HTTPFeedGateway implements IFeedGateway {
}
}

return api.ssr().request({
return api.request({
name: '/feeds GET',
params: {
types: data.filters.types,
Expand Down Expand Up @@ -154,7 +154,7 @@ export class HTTPFeedGateway implements IFeedGateway {
}

retrieveFeedItem(data: { entourageUuid: string; }) {
return api.ssr().request({
return api.request({
name: '/entourages/:entourageId GET',
pathParams: {
entourageUuid: data.entourageUuid,
Expand Down
4 changes: 2 additions & 2 deletions src/adapters/gateways/HTTPPOIsGateway.ts
Original file line number Diff line number Diff line change
Expand Up @@ -22,7 +22,7 @@ export class HTTPPOIsGateway implements IPOIsGateway {
}
}

return api.ssr().request({
return api.request({
name: '/pois GET',
params: {
v: 2,
Expand Down Expand Up @@ -61,7 +61,7 @@ export class HTTPPOIsGateway implements IPOIsGateway {
}

retrievePOI: IPOIsGateway['retrievePOI'] = (data) => {
return api.ssr().request({
return api.request({
name: '/pois/:poiUuid GET',
params: {
v: 2,
Expand Down
33 changes: 24 additions & 9 deletions src/adapters/storage/CookiesAuthUserTokenStorage.ts
Original file line number Diff line number Diff line change
@@ -1,19 +1,34 @@
// import { NextPageContext } from 'next'
import { getTokenFromCookies, setTokenIntoCookies } from 'src/core/services'
import { NextPageContext } from 'next'
import { parseCookies, setCookie } from 'nookies'
import { constants } from 'src/constants'
import { createAnonymousUser } from 'src/core/services'
import { IAuthUserTokenStorage } from 'src/core/useCases/authUser'

export class CookiesAuthUserTokenStorage implements IAuthUserTokenStorage {
// constructor(private nextContext: NextPageContext) {}
static authToken = ''

setToken(token: string) {
setTokenIntoCookies(token)
static async initToken(ctx?: NextPageContext): Promise<void> {
const token = CookiesAuthUserTokenStorage.getTokenFromCookie(ctx) || await createAnonymousUser()
CookiesAuthUserTokenStorage.setToken(token, ctx)
}

static getTokenFromCookie(ctx?: NextPageContext): string | null {
return parseCookies(ctx)[constants.AUTH_TOKEN_KEY]
}

getToken() {
return getTokenFromCookies()
static setToken(authToken: string, ctx?: NextPageContext): void {
setCookie(ctx, constants.AUTH_TOKEN_KEY, authToken, {
maxAge: constants.AUTH_TOKEN_TTL,
path: '/',
})
CookiesAuthUserTokenStorage.authToken = authToken
}

setToken(token: string) {
CookiesAuthUserTokenStorage.setToken(token)
}

removeToken() {
this.setToken('')
getToken(ctx?: NextPageContext) {
return CookiesAuthUserTokenStorage.getTokenFromCookie(ctx)
}
}
5 changes: 1 addition & 4 deletions src/containers/Nav/useOnClickLogout.ts
Original file line number Diff line number Diff line change
@@ -1,14 +1,11 @@
import { useCallback } from 'react'
import { useDispatch } from 'react-redux'
import { setTokenIntoCookies, createAnonymousUser } from 'src/core/services'
import { authUserActions } from 'src/core/useCases/authUser'

export function useOnClickLogout() {
const dispatch = useDispatch()

return useCallback(async () => {
setTokenIntoCookies('')
await createAnonymousUser()
dispatch(authUserActions.setUser(null))
dispatch(authUserActions.logout())
}, [dispatch])
}
7 changes: 7 additions & 0 deletions src/containers/PersistedStore/PersistedStore.tsx
Original file line number Diff line number Diff line change
@@ -1,11 +1,18 @@
import { PersistGate } from 'redux-persist/integration/react'
import React from 'react'
import { useStore } from 'react-redux'
import { SplashScreen } from 'src/components/SplashScreen'
import { isSSR } from 'src/utils/misc'

export function PersistedStore(props: { children: React.ReactNode; }) {
const { children } = props
const store = useStore()

if (isSSR) {
// TODO fix 'Expected server HTML to contain a matching <img> in <div>'.
return <>{children}</>
}

return (
// @ts-expect-error
// eslint-disable-next-line
Expand Down
25 changes: 2 additions & 23 deletions src/core/api/api.ts
Original file line number Diff line number Diff line change
@@ -1,8 +1,6 @@
import axios, { AxiosRequestConfig, AxiosPromise, Method } from 'axios'
import { NextPageContext } from 'next'
import { Config, Response } from 'typescript-request-schema'
import { env } from 'src/core/env'
import { createAnonymousUser, getTokenFromCookies } from 'src/core/services'
import { AnyToFix } from 'src/utils/types'
import { addAxiosInterceptors } from './interceptors'
import { schema, TypeScriptRequestSchemaConf } from './schema'
Expand Down Expand Up @@ -44,29 +42,10 @@ const request: Request = (config) => {

addAxiosInterceptors(axiosInstance)

type APIInstanceWithSSR = {
type APIInstance = {
request: typeof request;
ssr: (ctx?: NextPageContext) => {
request: typeof request;
};
}

export const api: APIInstanceWithSSR = {
export const api: APIInstance = {
request,
ssr: (ctx?) => ({
request: async (config) => {
const token = getTokenFromCookies(ctx) || await createAnonymousUser(ctx)

const configWithToken = {
...config,
params: {
// @ts-expect-error
...(config.params || {}),
token,
},
}

return request(configWithToken)
},
}),
}
4 changes: 2 additions & 2 deletions src/core/api/interceptors.ts
Original file line number Diff line number Diff line change
@@ -1,13 +1,13 @@
import { AxiosInstance } from 'axios'
import humps from 'humps'
import { CookiesAuthUserTokenStorage } from 'src/adapters/storage/CookiesAuthUserTokenStorage'
import { env } from 'src/core/env'
import { getTokenFromCookies } from 'src/core/services'
import { notifServerError } from 'src/utils/misc'

export function addAxiosInterceptors(client: AxiosInstance) {
function getUserToken(): string | null {
// TODO: improve with token cache in memory for browser side
return getTokenFromCookies()
return CookiesAuthUserTokenStorage.authToken
}

client.interceptors.request.use((request) => {
Expand Down
9 changes: 6 additions & 3 deletions src/core/boostrapStore.ts
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@ import { HTTPFeedGateway } from 'src/adapters/gateways/HTTPFeedGateway'
import { GeolocationService } from 'src/adapters/services/GeolocationService'
import { CookiesAuthUserTokenStorage } from 'src/adapters/storage/CookiesAuthUserTokenStorage'
import { LocalAuthUserSensitizationStorage } from 'src/adapters/storage/LocalAuthUserSensitizationStorage'
import { isSSR } from 'src/utils/misc'
import { configureStore } from './configureStore'
import { AppDependencies } from './useCases/Dependencies'
import { authUserSaga } from './useCases/authUser'
Expand Down Expand Up @@ -46,9 +47,11 @@ export function bootstrapStore() {
dependencies,
})

// @ts-expect-error
// eslint-disable-next-line
store.__persistor = persistStore(store)
if (!isSSR) {
// @ts-expect-error
// eslint-disable-next-line
store.__persistor = persistStore(store)
}

return { store }
}
Expand Down
14 changes: 0 additions & 14 deletions src/core/services/authToken.ts

This file was deleted.

10 changes: 2 additions & 8 deletions src/core/services/createAnonymousUser.ts
Original file line number Diff line number Diff line change
@@ -1,15 +1,9 @@
import { NextPageContext } from 'next'
import { api } from 'src/core/api'
import { setTokenIntoCookies } from './authToken'

export async function createAnonymousUser(ctx?: NextPageContext): Promise<string> {
export async function createAnonymousUser(): Promise<string> {
const anonymousUsersRes = await api.request({
name: '/anonymous_users POST',
})

const anonymousToken = anonymousUsersRes.data.user.token

setTokenIntoCookies(anonymousToken, ctx)

return anonymousToken
return anonymousUsersRes.data.user.token
}
1 change: 0 additions & 1 deletion src/core/services/index.ts
Original file line number Diff line number Diff line change
@@ -1,2 +1 @@
export * from './authToken'
export * from './createAnonymousUser'
1 change: 0 additions & 1 deletion src/core/useCases/authUser/IAuthUserTokenStorage.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,4 @@
export interface IAuthUserTokenStorage {
getToken(): string | null;
setToken(token: string): void;
removeToken(): void;
}
2 changes: 0 additions & 2 deletions src/core/useCases/authUser/TestAuthUserTokenStorage.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,4 @@ export class TestAuthUserTokenStorage implements IAuthUserTokenStorage {
getToken = jestFn<IAuthUserTokenStorage['getToken']>('getToken')

setToken = jestFn<IAuthUserTokenStorage['setToken']>('setToken')

removeToken = jestFn<IAuthUserTokenStorage['removeToken']>('removeToken')
}
8 changes: 8 additions & 0 deletions src/core/useCases/authUser/authUser.actions.ts
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,7 @@ export const AuthUserActionType = {
HIDE_SENSITIZATION_POPUP: 'AUTH/HIDE_SENSITIZATION_POPUP',
UPDATE_USER: 'AUTH/UPDATE_USER',
UPDATE_USER_SUCCEEDED: 'AUTH/UPDATE_USER_SUCCEEDED',
LOGOUT: 'AUTH/LOGOUT',
} as const

export type AuthUserActionType = keyof typeof AuthUserActionType;
Expand Down Expand Up @@ -181,6 +182,12 @@ function updateUserSuccess(payload: { user: NonNullable<AuthUserState['user']>;
}
}

function logout() {
return {
type: AuthUserActionType.LOGOUT,
}
}

// ------------------------------------------------------------------------

export const publicActions = {
Expand All @@ -196,6 +203,7 @@ export const publicActions = {
setUser,
hideSensitizationPopup,
updateUser,
logout,
}

const privateActions = {
Expand Down
10 changes: 10 additions & 0 deletions src/core/useCases/authUser/authUser.saga.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,8 @@
import { call, put, getContext, select } from 'redux-saga/effects'
import { locationActions } from '../location'
import { CookiesAuthUserTokenStorage } from 'src/adapters/storage/CookiesAuthUserTokenStorage'
import { constants } from 'src/constants'
import { createAnonymousUser } from 'src/core/services'
import { CallReturnType } from 'src/core/utils/CallReturnType'
import { takeEvery } from 'src/core/utils/takeEvery'
import { PhoneLookUpResponse, IAuthUserGateway } from './IAuthUserGateway'
Expand All @@ -18,6 +20,7 @@ import {
validateSMSCode,
SMSCodeValidationsError,
} from './authUser.validations'
import { authUserActions } from './index'

export interface Dependencies {
authUserGateway: IAuthUserGateway;
Expand Down Expand Up @@ -236,6 +239,12 @@ function* updateUserSaga(action: AuthUserActions['updateUser']) {
yield put(actions.updateUserSuccess({ user: response }))
}

function* logoutSaga() {
const anonymousToken: CallReturnType<typeof createAnonymousUser> = yield call(createAnonymousUser)
CookiesAuthUserTokenStorage.setToken(anonymousToken)
yield put(authUserActions.setUser(null))
}

export function* authUserSaga() {
yield takeEvery(AuthUserActionType.PHONE_LOOK_UP, phoneLookUpSaga)
yield takeEvery(AuthUserActionType.CREATE_ACCOUNT, createAccountSaga)
Expand All @@ -248,5 +257,6 @@ export function* authUserSaga() {
yield takeEvery(AuthUserActionType.SET_USER, showSensitizationPopupSaga)
yield takeEvery(AuthUserActionType.HIDE_SENSITIZATION_POPUP, hideSensitizationPopupSaga)
yield takeEvery(AuthUserActionType.UPDATE_USER, updateUserSaga)
yield takeEvery(AuthUserActionType.LOGOUT, logoutSaga)
}

36 changes: 33 additions & 3 deletions src/core/useCases/authUser/authUser.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -38,7 +38,6 @@ function createSilentAuthUserTokenStorage() {
const authUserTokenStorage = new TestAuthUserTokenStorage()
authUserTokenStorage.getToken.mockImplementation()
authUserTokenStorage.setToken.mockImplementation()
authUserTokenStorage.removeToken.mockImplementation()

return authUserTokenStorage
}
Expand Down Expand Up @@ -868,8 +867,38 @@ describe('Auth User', () => {
expect(authUserTokenStorage.setToken).toHaveBeenCalledTimes(1)
})

// TODO
// it.skip('should remove user token on logout', () => {})
// TODO Fix test
it(`
Given initial state
And user is logged in
When user logs out
Then the user should be set to null
And the anonymous user token should be set into cookies`,
async () => {
/* const authUserGateway = new TestAuthUserGateway()
const authUserTokenStorage = new TestAuthUserTokenStorage()
const user = createUser(false, false)
// const anonymousUser = await createAnonymousUser()
const store = configureStoreWithAuthUser({
initialAppState: {
authUser: {
...defaultAuthUserState,
user,
},
location: {
...defaultLocationState,
},
},
dependencies: { authUserGateway, authUserTokenStorage },
})
store.dispatch(publicActions.logout())
await store.waitForActionEnd()
expect(selectUser(store.getState())).toEqual(null)
// expect(authUserTokenStorage.getToken).toEqual(anonymousUser) */
})
})

describe('Giver user is not set', () => {
Expand Down Expand Up @@ -904,6 +933,7 @@ describe('Auth User', () => {
expect(firebaseService.setUser).toHaveBeenCalledWith(user.id.toString())
})

// TODO CHANGE TEST
it(`
Given initial state
When user is logged out
Expand Down
3 changes: 2 additions & 1 deletion src/core/useCases/location/location.reducer.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
import { constants } from 'src/constants'
import { persistReducer } from 'src/core/utils/persistReducer'
import { isSSR } from 'src/utils/misc'
import { LocationAction, LocationActionType } from './location.actions'

export interface LocationState {
Expand Down Expand Up @@ -89,6 +90,6 @@ function locationPureReducer(
}
}

export const locationReducer = persistReducer('location', locationPureReducer, {
export const locationReducer = isSSR ? locationPureReducer : persistReducer('location', locationPureReducer, {
blacklist: ['isInit'],
})
Loading

0 comments on commit d8ffa69

Please sign in to comment.