From 0c91117a903d33337c13f1c01714966d7ddf1830 Mon Sep 17 00:00:00 2001 From: Henry Orozco Date: Wed, 4 Dec 2024 12:38:27 -0600 Subject: [PATCH] feat: add user_id cookie --- next.config.js | 2 +- package-lock.json | 13 +++++----- src/components/HOCs/withAuthGuard.spec.tsx | 2 +- src/components/molecules/socialLogin.tsx | 28 ++++++++++++---------- src/components/organisms/navigation.tsx | 13 ++++++---- src/components/organisms/profileForm.tsx | 4 ++-- src/components/organisms/registerForm.tsx | 24 +++++++++++++------ src/containers/account/register.spec.tsx | 20 ++++++++++++++-- src/lib/api/fetchApi.spec.ts | 2 +- src/lib/api/login.ts | 7 ++++-- src/lib/api/useLogout.ts | 4 ++-- src/lib/cookies.ts | 21 +++++++++++----- src/lib/test/loadAuthGuardData.ts | 2 +- 13 files changed, 93 insertions(+), 49 deletions(-) diff --git a/next.config.js b/next.config.js index 70119c320..074666dad 100644 --- a/next.config.js +++ b/next.config.js @@ -62,7 +62,7 @@ module.exports = withBundleAnalyzer( has: [ { type: 'cookie', - key: 'avSession', + key: 'session_token', value: undefined, }, { diff --git a/package-lock.json b/package-lock.json index 1ab66f786..236068798 100644 --- a/package-lock.json +++ b/package-lock.json @@ -17,7 +17,6 @@ "@silvermine/videojs-chromecast": "^1.5.0", "@tanstack/react-query": "^5.56.2", "@tanstack/react-query-devtools": "^5.56.2", - "caniuse-lite": "^1.0.30001669", "clsx": "^2.1.1", "cookie": "^0.7.0", "dayjs": "^1.11.13", @@ -4782,9 +4781,9 @@ "dev": true }, "node_modules/@graphql-tools/utils": { - "version": "10.6.1", - "resolved": "https://registry.npmjs.org/@graphql-tools/utils/-/utils-10.6.1.tgz", - "integrity": "sha512-XHl0/DWkMf/8Dmw1F3RRoMPt6ZwU4J707YWcbPjS+49WZNoTVz6f+prQ4GuwZT8RqTPtrRawnGU93AV73ZLTfQ==", + "version": "10.6.2", + "resolved": "https://registry.npmjs.org/@graphql-tools/utils/-/utils-10.6.2.tgz", + "integrity": "sha512-ABZHTpwiVR8oE2//NI/nnU3nNhbBpqMlMYyCF5cnqjLfhlyOdFfoRuhYEATEsmMfDg0ijGreULywK/SmepVGfw==", "dev": true, "license": "MIT", "dependencies": { @@ -11205,9 +11204,9 @@ } }, "node_modules/caniuse-lite": { - "version": "1.0.30001669", - "resolved": "https://registry.npmjs.org/caniuse-lite/-/caniuse-lite-1.0.30001669.tgz", - "integrity": "sha512-DlWzFDJqstqtIVx1zeSpIMLjunf5SmwOw0N2Ck/QSQdS8PLS4+9HrLaYei4w8BIAL7IB/UEDu889d8vhCTPA0w==", + "version": "1.0.30001686", + "resolved": "https://registry.npmjs.org/caniuse-lite/-/caniuse-lite-1.0.30001686.tgz", + "integrity": "sha512-Y7deg0Aergpa24M3qLC5xjNklnKnhsmSyR/V89dLZ1n0ucJIFNs7PgR2Yfa/Zf6W79SbBicgtGxZr2juHkEUIA==", "funding": [ { "type": "opencollective", diff --git a/src/components/HOCs/withAuthGuard.spec.tsx b/src/components/HOCs/withAuthGuard.spec.tsx index 1dad7d8f0..5486069c3 100644 --- a/src/components/HOCs/withAuthGuard.spec.tsx +++ b/src/components/HOCs/withAuthGuard.spec.tsx @@ -47,7 +47,7 @@ describe('withAuthGuard', () => { }); it('displays content on successful social login', async () => { - Cookies.get = jest.fn().mockReturnValue({ avSession: 'abc123' }); + Cookies.get = jest.fn().mockReturnValue({ session_token: 'abc123' }); const { getByText, queryByText } = await render(); diff --git a/src/components/molecules/socialLogin.tsx b/src/components/molecules/socialLogin.tsx index 03111f124..46a6495c9 100644 --- a/src/components/molecules/socialLogin.tsx +++ b/src/components/molecules/socialLogin.tsx @@ -11,7 +11,7 @@ import { FormattedMessage, useIntl } from 'react-intl'; import { useRegisterSocialMutation } from '~containers/account/__generated__/register'; import { FACEBOOK_APP_ID, GOOGLE_CLIENT_ID } from '~lib/constants'; -import { setSessionToken } from '~lib/cookies'; +import { setSessionTokenAndUserId } from '~lib/cookies'; import useDidUnmount from '~lib/useDidUnmount'; import { UserSocialServiceName } from '~src/__generated__/graphql'; import { analytics } from '~src/lib/analytics'; @@ -39,27 +39,29 @@ export default function SocialLogin({ const { mutate: mutateSocial, isSuccess: isSuccessSocial } = useRegisterSocialMutation({ onSuccess: async (response, variables) => { - const errors = response?.loginSocial.errors || []; - const token = response?.loginSocial.authenticatedUser?.sessionToken; - - if (token && !errors.length) { - setSessionToken(token); - const user = response?.loginSocial.authenticatedUser?.user; - analytics.identify(user?.id + '', { - firstName: user?.givenName, - lastName: user?.surname, - email: user?.email, + const errors = response.loginSocial.errors || []; + const authenticatedUser = response.loginSocial.authenticatedUser; + + if (authenticatedUser && !errors.length) { + setSessionTokenAndUserId( + authenticatedUser.sessionToken, + authenticatedUser.user.id.toString(), + ); + analytics.identify(authenticatedUser.user.id + '', { + firstName: authenticatedUser.user.givenName, + lastName: authenticatedUser.user.surname, + email: authenticatedUser.user.email, source: 'Login', }); if (response?.loginSocial.isNewUser) { gtmPushEvent('sign_up', { sign_up_method: variables?.socialName, - user_id: user?.id, + user_id: authenticatedUser.user.id, }); } else { gtmPushEvent('sign_in', { sign_in_method: variables?.socialName, - user_id: user?.id, + user_id: authenticatedUser.user.id, }); } onSuccess ? onSuccess() : await queryClient.invalidateQueries(); diff --git a/src/components/organisms/navigation.tsx b/src/components/organisms/navigation.tsx index 31a3f8aa9..724a25bad 100644 --- a/src/components/organisms/navigation.tsx +++ b/src/components/organisms/navigation.tsx @@ -11,7 +11,11 @@ import DownloadAppButton from '~components/molecules/downloadAppButton'; import LanguageButton from '~components/molecules/languageButton'; import NavItem from '~components/molecules/navItem'; import Header from '~components/organisms/header'; -import { getSessionToken, setSessionToken } from '~lib/cookies'; +import { + getSessionToken, + getUserId, + setSessionTokenAndUserId, +} from '~lib/cookies'; import root from '~lib/routes'; import useLanguageRoute from '~lib/useLanguageRoute'; import { INavigationItem, useNavigationItems } from '~lib/useNavigationItems'; @@ -32,6 +36,7 @@ const Navigation = ({ onExit }: { onExit: () => void }): JSX.Element => { const router = useRouter(); const [submenu, setSubmenu] = useState(''); const sessionToken = getSessionToken(); + const userId = getUserId(); useEffect(() => { const onRouteChange = (url: string) => { @@ -48,10 +53,10 @@ const Navigation = ({ onExit }: { onExit: () => void }): JSX.Element => { }, []); useEffect(() => { - if (sessionToken) { - setSessionToken(sessionToken); + if (sessionToken && userId) { + setSessionTokenAndUserId(sessionToken, userId); } - }, [router.asPath, sessionToken]); + }, [router.asPath, sessionToken, userId]); const { user } = useIsAuthenticated(); diff --git a/src/components/organisms/profileForm.tsx b/src/components/organisms/profileForm.tsx index 62c3c50e0..9ac78e4cb 100644 --- a/src/components/organisms/profileForm.tsx +++ b/src/components/organisms/profileForm.tsx @@ -9,7 +9,7 @@ import Checkbox from '~components/molecules/form/checkbox'; import Input from '~components/molecules/form/input'; import Modal from '~components/organisms/modal'; import { refetchUserQueries, resetUserQueries } from '~src/lib/api/login'; -import { clearSessionToken } from '~src/lib/cookies'; +import { clearSessionTokenAndUserId } from '~src/lib/cookies'; import root from '~src/lib/routes'; import useLanguageRoute from '~src/lib/useLanguageRoute'; @@ -83,7 +83,7 @@ export default function ProfileForm(): JSX.Element { const { isPending, mutate: deleteAccountMutate } = useDeleteAccountMutation({ onSuccess: async () => { - clearSessionToken(); + clearSessionTokenAndUserId(); resetUserQueries(queryClient); router.push(root.lang(languageRoute).discover.get()); }, diff --git a/src/components/organisms/registerForm.tsx b/src/components/organisms/registerForm.tsx index 3a2514780..aabb716b9 100644 --- a/src/components/organisms/registerForm.tsx +++ b/src/components/organisms/registerForm.tsx @@ -4,7 +4,7 @@ import { FormattedMessage, useIntl } from 'react-intl'; import Button from '~components/molecules/button'; import Input from '~components/molecules/form/input'; import { useRegisterMutation } from '~containers/account/__generated__/register'; -import { setSessionToken } from '~lib/cookies'; +import { setSessionTokenAndUserId } from '~lib/cookies'; import { gtmPushEvent } from '~src/utils/gtm'; import { analytics } from '../../lib/analytics'; @@ -33,11 +33,21 @@ function RegisterForm({ showLogin, onSuccess }: Props): JSX.Element { const intl = useIntl(); useEffect(() => { - if (dataRegister?.signup.errors.length) { - setErrors(dataRegister?.signup.errors.map((e) => e.message)); - } else if (dataRegister?.signup.authenticatedUser?.sessionToken) { - setSessionToken(dataRegister?.signup.authenticatedUser?.sessionToken); - analytics.identify(dataRegister?.signup.authenticatedUser?.user.id + '', { + if (!dataRegister) { + return; + } + + const { + signup: { errors, authenticatedUser }, + } = dataRegister; + if (errors.length) { + setErrors(errors.map((e) => e.message)); + } else if (authenticatedUser) { + setSessionTokenAndUserId( + authenticatedUser.sessionToken, + authenticatedUser.user.id.toString(), + ); + analytics.identify(authenticatedUser.user.id + '', { firstName: firstName, lastName: lastName, email: email, @@ -45,7 +55,7 @@ function RegisterForm({ showLogin, onSuccess }: Props): JSX.Element { }); gtmPushEvent('sign_up', { sign_up_method: 'email', - user_id: dataRegister.signup.authenticatedUser.user.id, + user_id: authenticatedUser.user.id, }); onSuccess(); diff --git a/src/containers/account/register.spec.tsx b/src/containers/account/register.spec.tsx index 75df84ad5..f72b7eb7e 100644 --- a/src/containers/account/register.spec.tsx +++ b/src/containers/account/register.spec.tsx @@ -192,6 +192,19 @@ describe('register page', () => { it('renders social login success', async () => { const { getByText } = await renderPage(); + when(fetchApi) + .calledWith(RegisterSocialDocument, expect.anything()) + .mockResolvedValue({ + loginSocial: { + authenticatedUser: { + sessionToken: 'the_token', + user: { + id: 1, + }, + }, + }, + }); + await userEvent.click(await screen.findByText('Sign up with Facebook')); await waitFor(() => { @@ -224,6 +237,9 @@ describe('register page', () => { loginSocial: { authenticatedUser: { sessionToken: 'the_token', + user: { + id: 1, + }, }, }, }); @@ -233,7 +249,7 @@ describe('register page', () => { await userEvent.click(await screen.findByText('Sign up with Facebook')); await waitFor(() => { - expect(Cookie.set).toBeCalledWith('avSession', 'the_token', { + expect(Cookie.set).toBeCalledWith('session_token', 'the_token', { expires: 14, }); }); @@ -273,7 +289,7 @@ describe('register page', () => { }); it('does not display form if user logged in', async () => { - Cookie.get = jest.fn().mockReturnValue({ avSession: 'abc123' }); + Cookie.get = jest.fn().mockReturnValue({ session_token: 'abc123' }); const { queryByPlaceholderText } = await renderPage(); diff --git a/src/lib/api/fetchApi.spec.ts b/src/lib/api/fetchApi.spec.ts index d634d8e46..65a9a15ed 100644 --- a/src/lib/api/fetchApi.spec.ts +++ b/src/lib/api/fetchApi.spec.ts @@ -24,7 +24,7 @@ const setRequest = (cookie = '') => { describe('fetchApi', () => { it('uses saved request', async () => { mockFetchResponse(); - setRequest('avSession=the_token'); + setRequest('session_token=the_token'); await fetchApi(noopQuery); diff --git a/src/lib/api/login.ts b/src/lib/api/login.ts index 176c5a8d8..32065951d 100644 --- a/src/lib/api/login.ts +++ b/src/lib/api/login.ts @@ -1,6 +1,6 @@ import { QueryClient } from '@tanstack/react-query'; -import { setSessionToken } from '~lib/cookies'; +import { setSessionTokenAndUserId } from '~lib/cookies'; import { gtmPushEvent } from '~src/utils/gtm'; import { analytics } from '../analytics'; @@ -42,7 +42,10 @@ export async function login(email: string, password: string): Promise { login: { authenticatedUser, errors }, } = await _login({ email, password }); if (authenticatedUser) { - setSessionToken(authenticatedUser.sessionToken); + setSessionTokenAndUserId( + authenticatedUser.sessionToken, + authenticatedUser.user.id.toString(), + ); const user = authenticatedUser.user; analytics.identify(user.id + '', { firstName: user.givenName, diff --git a/src/lib/api/useLogout.ts b/src/lib/api/useLogout.ts index d4f3572f1..837fac2f8 100644 --- a/src/lib/api/useLogout.ts +++ b/src/lib/api/useLogout.ts @@ -1,6 +1,6 @@ import { useQueryClient } from '@tanstack/react-query'; -import { clearSessionToken } from '~lib/cookies'; +import { clearSessionTokenAndUserId } from '~lib/cookies'; import isServerSide from '~lib/isServerSide'; import { resetUserQueries } from './login'; @@ -8,7 +8,7 @@ import { resetUserQueries } from './login'; export function useLogout(): Promise { const queryClient = useQueryClient(); - clearSessionToken(); + clearSessionTokenAndUserId(); if (!isServerSide()) { window.Beacon && window.Beacon('logout'); diff --git a/src/lib/cookies.ts b/src/lib/cookies.ts index a7b8bbcb8..35d5cddbd 100644 --- a/src/lib/cookies.ts +++ b/src/lib/cookies.ts @@ -2,13 +2,20 @@ import cookie from 'cookie'; import { IncomingMessage } from 'http'; import JSCookie from 'js-cookie'; -const SESSION_KEY = 'avSession'; +const SESSION_TOKEN_KEY = 'session_token'; +const USER_ID_KEY = 'user_id'; const LANGUAGE_KEY = 'lang'; export function getSessionToken( req: IncomingMessage | null = null, ): string | undefined { - return getCookies(req)[SESSION_KEY]; + return getCookies(req)[SESSION_TOKEN_KEY]; +} + +export function getUserId( + req: IncomingMessage | null = null, +): string | undefined { + return getCookies(req)[USER_ID_KEY]; } export function getLanguageId( @@ -17,16 +24,18 @@ export function getLanguageId( return getCookies(req)[LANGUAGE_KEY]; } -export function setSessionToken(token: string): void { - JSCookie.set(SESSION_KEY, token, { expires: 14 }); +export function setSessionTokenAndUserId(token: string, userId: string): void { + JSCookie.set(SESSION_TOKEN_KEY, token, { expires: 14 }); + JSCookie.set(USER_ID_KEY, userId, { expires: 14 }); } export function setLanguageId(lang: string): void { JSCookie.set(LANGUAGE_KEY, lang, { expires: 365 }); } -export function clearSessionToken(): void { - JSCookie.remove(SESSION_KEY); +export function clearSessionTokenAndUserId(): void { + JSCookie.remove(SESSION_TOKEN_KEY); + JSCookie.remove(USER_ID_KEY); } function getCookies(req: IncomingMessage | null): { diff --git a/src/lib/test/loadAuthGuardData.ts b/src/lib/test/loadAuthGuardData.ts index 8f71d8546..3c49decac 100644 --- a/src/lib/test/loadAuthGuardData.ts +++ b/src/lib/test/loadAuthGuardData.ts @@ -5,7 +5,7 @@ import { fetchApi } from '~lib/api/fetchApi'; import { GetIsAuthenticatedDocument } from '~lib/hooks/__generated__/useIsAuthenticated'; export function loadAuthGuardData(email: any = 'the_email'): void { - Cookie.get = jest.fn().mockReturnValue({ avSession: 'abc123' }); + Cookie.get = jest.fn().mockReturnValue({ session_token: 'abc123' }); when(fetchApi) .calledWith(GetIsAuthenticatedDocument, expect.anything())